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
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Entity display name |
source_id | string | no | Unique ID from source system (enables upsert) |
vertical_data | object | no | Structured 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
| Field | Type | Required | Description |
|---|---|---|---|
file | file | yes | CSV, JSON, NDJSON, or XML file (max 50 MB) |
name_column | string | no | CSV 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}
EOFResponse (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": {} }
}
]