Import jobs
An Import Job ingests many products at once. Row failures are isolated, one bad row does not fail the batch, and you correct and re-submit against a generated template.
Lifecycle
POST /api/v2/import/jobs (multipart: file=CSV, UTF-8, ≤10 MB, magic-byte validated) → 202 { jobId }
GET /api/v2/import/jobs/{jobId} → status / preview
GET /api/v2/import/jobs/{jobId}/errors → row-level failures (isolated)
GET /api/v2/import/jobs/{jobId}/correction-template → fixable re-upload template
POST /api/v2/import/jobs/{jobId}/confirm → applyGet the right template first: GET /api/v2/schemas/categories/{category}/import-template?jurisdiction=EU renders the CSV columns from the category's effective schema, fill it in and upload it via POST /api/v2/import/jobs; the columns round-trip by construction. Also: POST /api/v2/import (single-shot CSV or XLSX, ≤ 10 MB) and GET /api/v2/import/templates/{regulationCode} (same generator, keyed by regulation code). Re-submitting the same file is a no-op.
Malware scan: both upload routes scan every file before anything is parsed, stored or queued, and refuse the upload rather than import it unscanned. 413 FILE_TOO_LARGE_TO_SCAN: the file is larger than 10 MB, the most the scan accepts. 422 MALWARE_FOUND: the scan found harmful content; nothing was imported, and the response carries a reference to quote to support. 503 SCAN_UNAVAILABLE: the scan could not run, so nothing was imported; retry after the Retry-After interval. A request body larger than 12 MB is refused with 413 before it is read, and one that takes longer than 60 seconds to arrive is refused with 408.
Machine on-ramp
The JSON batch path ERP-style integrations use, no multipart, API-key auth:
POST /api/v2/ingest/products (scope: ingest:write)
{ "sourceSystem": "erp-middleware",
"products": [ { "referenceId": "erp-4711", // REQUIRED, per-row idempotency key
"name": "EcoCell Battery Pack", // REQUIRED
"description": "…", "gtin": "…", // optional
"category": "Electronics", // optional display-name enum
"materials": [{ "name": "lithium", "percentage": 5.2 }],
"manufacturing": { "facility": "Gigafactory 1", "country": "DE" },
"customData": { "line": "A" } } ] }
→ 202 { jobId, links: { status } }
GET /api/v2/ingest/{jobId} → job status / row outcomes (poll this)The item shape is the strict ERP-flavoured ingest schema (unknown keys rejected), NOT the create-product shape. referenceId is the per-row identity: re-submitting the same rows does not duplicate products. Completion is observed by polling the status URL returned with the 202; that is the only completion mechanism for this path, and the import.completed / import.failed webhook events are not emitted for it.
callbackUrl is refused 400 CALLBACK_URL_UNSUPPORTED, whatever the URL. The field used to be accepted, and it was never called. Omit it and poll the status URL returned with the 202.POST /api/v2/import/aasx accepts a multipart .aasx (OPC/ZIP) package and creates a Draft product through the canonical create contract; XML-only payloads are refused honestly.