Imports API
Ce contenu n’est pas encore disponible dans votre langue.
POST /api/v1/imports/{kind} loads a whole file of customers or transactions in one request. It is
what Settings → Data import uses; this page is for scripting it and for checking exactly what
it returns. For a step-by-step guide, the column tables and fixes for common errors, see
Importing a file.
Each row is checked against the same rules as the ingest API and loaded through the same idempotent path, so importing a file twice only reports duplicates.
Before you start
Section titled “Before you start”Who can call it. A signed-in person with settings.manage (by default the
Administrator). An API key gets 401 sign in required; a person without the permission gets
403 missing permission settings.manage. Your systems should send data through the
ingest API with an API key instead; this endpoint is for people loading files.
Signing in with curl. Sign in once and keep the session cookie in a file, as described in
Alerts and cases API: before you start, then add
-b ~/.sp-cookies to the requests below.
Where it runs. Imports are served by processes running the api role. If none of them can
reach Kafka and the database, the request fails with 503 imports need the ingest service on this node.
POST /api/v1/imports/{kind}
Section titled “POST /api/v1/imports/{kind}”| Permission | settings.manage |
| Path | kind is transactions or customers |
| Body | The file itself: CSV with a header row (Content-Type: text/csv), or JSON (application/json) as an array, {"items": [...]} or one object per line |
| Query | format: csv or json. Optional; when omitted, a body starting with [ or { is JSON and anything else is CSV |
| Limits | 50,000 rows; SP_INGEST_MAX_BODY_BYTES (8 MB by default). Imports get their own deadlines, 5 minutes to read the request and 10 minutes to write the response, instead of SP_HTTP_*_TIMEOUT; a reverse proxy in front needs a body limit and timeouts at least that large |
| Times | CSV times without Z or an offset are read in the workspace’s time zone; JSON times must be RFC 3339 with a zone |
The CSV columns are listed in Importing a file: the columns. JSON records use the field names of the ingest API (transactions, customers).
Response: 200 OK
Section titled “Response: 200 OK”The request succeeds even when some rows are rejected; check the counts.
{ "rows": 1200, "accepted": 1187, "duplicate": 10, "stale": 0, "rejected": 3, "retry": 0, "errors": [ { "line": 14, "message": "amount \"1.234\" has more decimals than GHS allows" }, { "line": 233, "message": "expected value to be one of \"in, out, internal\" (direction: debit)" }, { "line": 871, "message": "no FX rate for USD→GHS on or before 2026-09-30; load rates via /api/v1/ingest/fx-rates" } ]}| Field | Meaning |
|---|---|
rows |
Data rows in the file, counting the ones that couldn’t be read |
accepted |
New records stored |
duplicate |
Records already held with exactly the same content. Nothing changed |
stale |
Customer records whose version is not newer than the one held (JSON only; CSV has no version column). Nothing changed. See Versions |
rejected |
Rows that failed a check |
retry |
Rows not stored because of a temporary problem. Send the file again |
errors |
Up to 200 problems as {line, message}. Problems from every stage (reading, validation, loading) are sorted by line, then cut to the first 200. In CSV, line 1 is the header. In newline-delimited JSON, line is the physical line in the file; in a JSON array or {"items": …}, it is the item’s position from 1 |
The statuses mean exactly what they mean in the ingest API; see Responses and statuses.
Errors for the whole request
Section titled “Errors for the whole request”| Status | When | Example detail |
|---|---|---|
401 |
Not signed in, or an API key | sign in required |
403 |
Missing settings.manage |
missing permission settings.manage |
413 |
More than 50,000 rows, or a body over the size limit | 60000 rows; split the file into parts of up to 50000 |
422 |
The file can’t be read at all: no header, missing required columns, or no data rows | missing column(s): occurred_at, currency |
503 |
This server can’t load data | imports need the ingest service on this node |
A body over the size limit is refused before SpeculaPro reads it, with a plain-text
request body too large rather than a JSON problem.
Examples
Section titled “Examples”Customers from a CSV file:
curl -s -b ~/.sp-cookies -H 'Content-Type: text/csv' \ --data-binary @customers.csv \ "$SP/api/v1/imports/customers"Transactions from a newline-delimited JSON export:
curl -s -b ~/.sp-cookies -H 'Content-Type: application/json' \ --data-binary @transactions.ndjson \ "$SP/api/v1/imports/transactions"Use --data-binary, not -d: -d removes the line breaks, which turns a CSV or one-per-line file
into a single line.