Overview
Batch imports run asynchronously: thePOST stages an import job and responds
immediately with 202 and a jobId. Your system then polls the job
(GET /api/import-jobs/{jobId}) until the import finishes and collects the full result
there — including the reasons for skipped or failed rows.
The import runs through the same engine as the in-app CSV importer: same validation,
same idempotent upsert, same result shape.
A
POST with a single object (no rows array) stays synchronous and returns
201 unchanged. Only the batch form (array or envelope) is async.Flow
-
Send the batch —
POSTa bare array of rows or an envelope object:On success:202with{ "jobId": "…", "status": "queued", "statusUrl": "/api/import-jobs/…" }(also sent as aLocationheader). Nothing is imported at this point. Row validation runs synchronously — a bad payload is rejected with400right away and no job is staged. -
Poll —
GET /api/import-jobs/{jobId}every ~2 seconds, backing off for large jobs (e.g. exponentially up to ~30 s).completedandfailedare terminal — stop polling then. -
Collect the result — once
statusis"completed",resultholds the merged import result (imported,updated,failed,skipped, … per entity). Failed rows carry stable, non-localized error keys.
Limits
- Max 5,000 rows per request. Above that:
413 { "error": "batchTooLarge", "maxBatchSize": 5000 }— nothing is staged. Page a larger sync across multiple requests. - ~4.5 MB request body (platform limit, enforced before our application runs).
- An empty array or a malformed envelope →
400.
Partial accept and idempotency
- Valid rows commit even when other rows land in
failed. - Replaying an identical import is safe: matched rows come back as skipped/updated, no duplicates are created.
Orders: replaceLines and the completeness rule
Importing an order replaces its entire line set. A request that names order O
must therefore carry all of O’s lines. If one order’s lines are split across
multiple requests, the second request would delete the previously imported lines.
The line-replacement gate turns that into a loud failure instead of silent data loss:
-
Default (
replaceLinesabsent orfalse): if the batch would delete stored lines, the whole job fails before the first write. The job’serrorfield contains:Resend the listed orders with their complete line sets. -
"replaceLines": trueauthorizes the replacement: the submitted line set becomes authoritative, even where lines are removed.
Error keys (stable, non-localized)
Retention
The job result stays pollable; the staged input payload is freed ~24 hours after the job finishes, and the job record is deleted ~30 days after creation. Poll and persist the result on your side within that window.Permissions
ThePOST requires the entity’s write scope (articles:write, suppliers:write,
orders:write); polling requires the matching read scope (implied by write). Jobs
are strictly tenant-bound: an unknown jobId and another company’s jobId return the
same 404.