QANATIX
API Reference

Blue-Green Replace

Zero-downtime data replacement using staged generations and atomic swaps.

Blue-Green Replace

Replace all data in a collection with zero downtime. Upload a new dataset into a staging generation, verify it, then atomically swap it into production. The old data stays searchable until the swap completes.

How it works

  1. UploadPOST /portal/collections/{name}/replace creates a staging generation and uploads your file into it.
  2. MonitorGET /portal/collections/{name}/replace-status tracks upload progress. All records must reach indexed status before swapping.
  3. SwapPOST /portal/collections/{name}/swap atomically promotes the staging generation in Postgres.
  4. Cancel (optional) — POST /portal/collections/{name}/cancel-replace archives the staging generation.

Only one replace operation can be in progress per collection at a time.

POST /portal/collections/{name}/replace

Start a blue-green replace by uploading a file. Accepts the same file formats as regular file upload: CSV, JSON, NDJSON, XML, and PDF.

curl -X POST "https://api.qanatix.com/api/v1/portal/collections/manufacturing/replace?record_type=product" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -F "file=@new-catalog.csv"

Query parameters

ParameterTypeRequiredDescription
record_typestringyesRecord type for the uploaded records
source_namestringnoSource label attached to each record
record_tagstringnoXML record tag name (required for XML files)

Quota check

Before upload begins, the endpoint checks that the replacement will not exceed your plan's record limit. The check uses the post-swap count: records in other collections plus the new records being uploaded.

other_records + new_records <= record_limit

If the check fails, the upload is rejected with a 402 error before any data is written.

Response (200)

{
  "upload_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "processing",
  "summary": {
    "submitted": 2500,
    "accepted": 2500,
    "rejected": 0,
    "dedup_skipped": 0
  },
  "errors": [],
  "metadata": {
    "processing_time_ms": 4500,
    "file_hash": "sha256:abc...",
    "reconciliation_passed": true,
    "pipeline_backpressure": false
  },
  "staging": {
    "generation": 2,
    "status": "staging",
    "collection": "manufacturing"
  }
}

The response combines the standard upload result fields with a staging object containing the new generation number and status.

Errors

StatusMeaning
402New dataset would exceed your plan's record limit
409A replace operation is already in progress for this collection
413Uploaded file exceeds the maximum size (50 MB)

GET /portal/collections/{name}/replace-status

Check the progress of a pending replace operation.

curl https://api.qanatix.com/api/v1/portal/collections/manufacturing/replace-status \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response (200)

{
  "has_pending_replace": true,
  "staging_generation": 2,
  "staging_status": "staging",
  "phase": "indexing",
  "total": 2500,
  "indexed": 2100,
  "pending": 350,
  "processing": 50,
  "failed": 0,
  "ready_to_swap": false
}

Response fields

FieldTypeDescription
has_pending_replacebooleanWhether a staging generation exists
staging_generationintegerGeneration number of the staging data
staging_statusstringAlways "staging" while in progress
phasestringCurrent phase of the replace operation (see below)
totalintegerTotal records in the staging generation
indexedintegerRecords that are indexed and searchable
pendingintegerRecords waiting to be processed
processingintegerRecords currently being processed
failedintegerRecords that failed processing
ready_to_swapbooleantrue when all records are indexed (no pending, processing, or failed)

When has_pending_replace is false, all other fields are null.

Replace phases

The phase field tracks the lifecycle of a replace operation. Phases progress in this order:

PhaseDescription
uploadingFile is being uploaded and parsed
queuedRecords have been created and are waiting to be processed
indexingRecords are actively being indexed
completed_with_errorsIndexing finished but some records failed (check failed count)
readyAll records are indexed and the staging generation is ready to swap
processingA swap or cancel operation is currently in progress

Use the phase field to show detailed progress in your UI. The ready_to_swap boolean remains the authoritative signal for whether a swap can proceed.

POST /portal/collections/{name}/swap

Promote the staging generation to production. This performs two atomic operations:

  1. Postgres swap — staging records become the active generation; old records are hard-deleted (permanently removed, not archived).

Search traffic sees no interruption.

curl -X POST https://api.qanatix.com/api/v1/portal/collections/manufacturing/swap \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Preconditions

All staging records must be indexed. Check ready_to_swap in the replace-status response before calling this endpoint.

Response (200)

{
  "collection": "manufacturing",
  "old_generation": 1,
  "new_generation": 2,
  "old_record_count": 2000,
  "new_record_count": 2500,
  "deleted": 2000
}

Response fields

FieldTypeDescription
collectionstringCollection name
old_generationintegerPrevious production generation (now deleted)
new_generationintegerNew active generation
old_record_countintegerNumber of records that were in the old generation
new_record_countintegerNumber of records now in production
deletedintegerNumber of old records permanently deleted

Errors

StatusMeaning
409No staging generation exists, or staging records are not fully indexed

POST /portal/collections/{name}/cancel-replace

Cancel a pending replace operation. Archives all staging records. The current production data is unaffected.

curl -X POST https://api.qanatix.com/api/v1/portal/collections/manufacturing/cancel-replace \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response (200)

{
  "collection": "manufacturing",
  "cancelled_generation": 2,
  "records_archived": 2500
}

Response fields

FieldTypeDescription
collectionstringCollection name
cancelled_generationintegerGeneration number that was cancelled
records_archivedintegerNumber of staging records archived

Errors

StatusMeaning
409No staging generation exists to cancel

Complete example

A typical replace workflow for refreshing a product catalog:

# 1. Upload new dataset
curl -X POST "https://api.qanatix.com/api/v1/portal/collections/manufacturing/replace?record_type=product&source_name=catalog-q2" \
  -H "Authorization: Bearer $JWT" \
  -F "file=@catalog-q2-2026.csv"

# 2. Poll until ready
while true; do
  STATUS=$(curl -s https://api.qanatix.com/api/v1/portal/collections/manufacturing/replace-status \
    -H "Authorization: Bearer $JWT")
  READY=$(echo $STATUS | jq -r '.ready_to_swap')
  if [ "$READY" = "true" ]; then break; fi
  echo "Waiting... $(echo $STATUS | jq -r '.indexed')/$(echo $STATUS | jq -r '.total') indexed"
  sleep 5
done

# 3. Swap into production
curl -X POST https://api.qanatix.com/api/v1/portal/collections/manufacturing/swap \
  -H "Authorization: Bearer $JWT"

Error responses

All blue-green replace endpoints follow the standard error format:

{
  "detail": "Replace already in progress for collection 'manufacturing'"
}
StatusMeaning
401Missing or invalid JWT token
402Record quota exceeded
404Collection not found
409Conflict (replace in progress, not ready, or no staging)
413File too large
422Validation error (missing record_type, invalid file format)

On this page