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
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Record display name |
source_id | string | no | Unique ID from source system (enables upsert) |
collection_data | object | no | Structured 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
| 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 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}
EOFResponse (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": {} }
}
]