QANATIX
API Reference

Ingestion API

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

Ingestion API

Push data into QANATIX. All ingestion routes include the vertical and entity type in the URL path.

POST /ingest/{vertical}/{entity_type}/batch

Ingest a batch of JSON records.

curl -X POST https://api.qanatix.com/api/v1/ingest/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",
        "vertical_data": {
          "part_number": "SS-M8-40-A2",
          "material": "Stainless Steel A2",
          "price_eur": 0.12
        }
      }
    ]
  }'

Record fields

FieldTypeRequiredDescription
namestringyesEntity display name
source_idstringnoUnique ID from source system (enables upsert)
vertical_dataobjectnoStructured data fields

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

Response (200)

{
  "ingestion_id": "a1b2c3d4-...",
  "status": "processing",
  "record_count": 150,
  "duplicate_count": 3,
  "entity_ids": ["550e8400-...", "660f9500-..."]
}

POST /ingest/{vertical}/{entity_type}/upload

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

curl -X POST https://api.qanatix.com/api/v1/ingest/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 entity name (default: name)

Response

Same format as batch ingestion.

POST /ingest/{vertical}/{entity_type}/pdf

Upload and extract text from a PDF.

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

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

Text is extracted using pymupdf4llm, producing clean markdown.

POST /ingest/{vertical}/{entity_type}/stream

Stream NDJSON records with backpressure.

curl -X POST https://api.qanatix.com/api/v1/ingest/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/ingest/{vertical}/{entity_type}

Webhook push with optional HMAC-SHA256 signature verification.

curl -X POST https://api.qanatix.com/api/v1/webhooks/ingest/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", "vertical_data": {"price": 12.50}}]}'

Set WEBHOOK_SECRET to enable signature verification.

GET /ingestions/{ingestion_id}

Check ingestion status.

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

Response (200)

{
  "id": "a1b2c3d4-...",
  "status": "completed",
  "record_count": 150,
  "duplicate_count": 3,
  "error_count": 0,
  "created_at": "2026-03-07T10:00:00Z",
  "completed_at": "2026-03-07T10:00:05Z"
}

GET /ingestions/{ingestion_id}/errors

Get dead letter queue entries for failed records.

curl https://api.qanatix.com/api/v1/ingestions/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", "vertical_data": {} }
  }
]

On this page