QANATIX
API Reference

Upload API

Batch upload, file upload, PDF, streaming, and webhook endpoints.

Upload API

Push data into QANATIX. All upload routes include the collection and record type in the URL path.

POST /upload/{collection}/{record_type}/batch

Upload a batch of JSON records.

curl -X POST https://api.qanatix.com/api/v1/upload/manufacturing/fastener/batch \
  -H "Authorization: Bearer sk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "records": [
      {
        "name": "Stainless Steel Bolt M8x40",
        "source_id": "ERP-001",
        "collection_data": {
          "part_number": "SS-M8-40-A2",
          "material": "Stainless Steel A2",
          "price_eur": 0.12
        }
      }
    ]
  }'

Record fields

FieldTypeRequiredDescription
namestringyesRecord display name
source_idstringnoUnique ID from source system (enables upsert)
collection_dataobjectnoStructured data fields

Maximum 10,000 records per batch. If source_id matches an existing record, it's updated (upsert).

Response (201)

{
  "upload_id": "a1b2c3d4-...",
  "status": "complete",
  "summary": {
    "submitted": 150,
    "accepted": 147,
    "rejected": 0,
    "dedup_skipped": 3
  },
  "errors": [],
  "metadata": {
    "processing_time_ms": 1230,
    "file_hash": "sha256:abc...",
    "reconciliation_passed": true,
    "pipeline_backpressure": false
  }
}

POST /upload/{collection}/{record_type}/upload

Upload a file (CSV, JSON, NDJSON, XML).

curl -X POST https://api.qanatix.com/api/v1/upload/manufacturing/product/upload \
  -H "Authorization: Bearer sk_live_abc123..." \
  -F "file=@products.csv"

Form parameters

FieldTypeRequiredDescription
filefileyesCSV, JSON, NDJSON, or XML file (max 50 MB)
name_columnstringnoCSV column to use as record name (default: name)

Response (201)

Same response format as batch upload.

POST /upload/{collection}/{record_type}/pdf

Upload and extract text from a PDF.

curl -X POST https://api.qanatix.com/api/v1/upload/manufacturing/document/pdf \
  -H "Authorization: Bearer sk_live_abc123..." \
  -F "file=@datasheet.pdf"

Chunking: PDFs with 1-3 pages are combined into one record. PDFs with 4+ pages create one record per page.

Text is extracted using pymupdf4llm, producing clean markdown.

POST /upload/{collection}/{record_type}/stream

Stream NDJSON records with backpressure.

curl -X POST https://api.qanatix.com/api/v1/upload/iot/sensor/stream \
  -H "Authorization: Bearer sk_live_abc123..." \
  -H "Content-Type: application/x-ndjson" \
  --data-binary @- << 'EOF'
{"name": "Reading 1", "temperature": 22.5}
{"name": "Reading 2", "temperature": 22.7}
EOF

Response (200)

{
  "accepted": 2,
  "parse_errors": 0,
  "buffer_size": 2,
  "flushed": false
}

Buffer limit: 10,000 records. Returns 429 if exceeded.

POST /webhooks/upload/{collection}/{record_type}

Webhook push with optional HMAC-SHA256 signature verification.

curl -X POST https://api.qanatix.com/api/v1/webhooks/upload/manufacturing/product \
  -H "Authorization: Bearer sk_live_abc123..." \
  -H "X-Webhook-Signature: sha256=..." \
  -H "Content-Type: application/json" \
  -d '{"records": [{"name": "Updated Product", "source_id": "PROD-789", "collection_data": {"price": 12.50}}]}'

Set WEBHOOK_SECRET to enable signature verification.

GET /uploads/{upload_id}

Check upload status.

curl https://api.qanatix.com/api/v1/uploads/a1b2c3d4-... \
  -H "Authorization: Bearer sk_live_abc123..."

Response (200)

{
  "id": "a1b2c3d4-...",
  "status": "complete",
  "summary": {
    "submitted": 150,
    "accepted": 147,
    "rejected": 0,
    "dedup_skipped": 3
  },
  "errors": [],
  "metadata": {
    "processing_time_ms": 5000,
    "file_hash": "sha256:abc...",
    "reconciliation_passed": true,
    "pipeline_backpressure": false
  },
  "created_at": "2026-03-07T10:00:00Z",
  "completed_at": "2026-03-07T10:00:05Z"
}

GET /uploads/{upload_id}/errors

Get dead letter queue entries for failed records.

curl https://api.qanatix.com/api/v1/uploads/a1b2c3d4-.../errors \
  -H "Authorization: Bearer sk_live_abc123..."

Response (200)

[
  {
    "record_index": 42,
    "error": "Schema validation failed: 'part_number' is required",
    "record_data": { "name": "Bad Record", "collection_data": {} }
  }
]

On this page