Imports
A mass import turns a spreadsheet into passports in bulk. Upload a file, preview it to build a column mapping, start the import, then poll it, inspect the rows that failed, and retry them with corrections. One mass import is far kinder to rate limits than firing thousands of single-passport creates.
The import model
- Name
object- Type
- string
- Description
Always
import.
- Name
id- Type
- string
- Description
Unique identifier, e.g.
import_00a1b2c3d4e5f607.
- Name
target- Type
- string
- Description
The passport the import runs under — usually the ontology's root passport. Nullable.
- Name
status- Type
- string
- Description
pending,importing,complete,complete_with_errors, orfailed. The last three are terminal.
- Name
file- Type
- string
- Description
The uploaded file being imported. Nullable.
- Name
levels- Type
- array
- Description
The mapping plan — one entry per phase, each pairing a
levelkey with its columnmapping. Nullable.
- Name
total_rows- Type
- integer
- Description
Data rows detected in the file.
- Name
processed_rows- Type
- integer
- Description
Rows imported so far.
- Name
failed_rows- Type
- integer
- Description
Rows that failed — validation plus infrastructure.
- Name
failed_validation_rows- Type
- integer
- Description
Rows rejected by validation.
- Name
failed_infra_rows- Type
- integer
- Description
Rows that hit a transient infrastructure error.
- Name
phases- Type
- array
- Description
Per-phase progress records for a multi-level import.
- Name
started_at- Type
- string
- Description
ISO 8601 timestamp when processing began.
nullwhile pending.
- Name
finished_at- Type
- string
- Description
ISO 8601 timestamp when the import reached a terminal status.
nulluntil then.
- Name
metadata- Type
- object
- Description
Caller-owned metadata bag, set at create time. Imports are immutable once started, so there is no update path.
GET /v1/importsalways reports{}here regardless of what the import carries — the list is served from an index with no metadata column.GET /v1/imports/{import}is truthful; do not reconcile against the list.
- Name
created_at- Type
- string
- Description
ISO 8601 timestamp of creation. Nullable.
The import row model
- Name
object- Type
- string
- Description
Always
import_row.
- Name
id- Type
- string
- Description
Row id, shaped
"{phase}#{zero-padded index}"— e.g."0#000017".
- Name
status- Type
- string
- Description
done,failed_validation,failed_infra, orretrying.
- Name
row- Type
- array
- Description
The raw spreadsheet cell values for this row.
- Name
errors- Type
- array
- Description
Validation or infrastructure errors, or
nullwhen the row succeeded.
- Name
attempts- Type
- integer
- Description
How many times this row has been tried.
- Name
passport- Type
- string
- Description
The
pass_…id created from this row oncedone, otherwisenull.
Start a mass import
Reads rows rows from an uploaded file, maps each spreadsheet column to an ontology property, and creates a passport per row under target. Build the mapping from the file preview, then poll the returned import until its status is terminal. Requires imports:write. Accepts an optional Idempotency-Key header.
Required attributes
- Name
file- Type
- string
- Description
The uploaded file's id (a
file_…id).
- Name
target- Type
- string
- Description
The passport to import under — usually the ontology's root passport (a
pass_…id).
- Name
rows- Type
- integer
- Description
Data row count from the file preview. Minimum 1, and never more than the file's size in bytes — a larger count is a stale-preview mistake and is rejected.
- Name
levels- Type
- array
- Description
At least one mapping phase. Each entry pairs a
levelkey with amappingof column index → property id (or the literalfriendly_id). Mapping keys are 0-based column indexes given as strings; unmapped columns are ignored.
The request is validated up front, before any row is processed, so a bad
mapping fails fast with 400 parameter_invalid (param naming the
offending field) instead of failing every row at import time: every
level must exist in the ontology, every mapped column must target a
property that level actually defines (friendly_id and none aside), and
rows cannot exceed the file's byte size. Re-derive rows and the
mapping from the file preview if either is
rejected.
Request
curl https://api.synexcloud.com/v1/imports \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Idempotency-Key: units-2026-07-12" \
-H "Content-Type: application/json" \
-d '{
"file": "file_9f2k7dq1w2e3r4t5",
"target": "pass_00a1b2c3d4e5f607",
"rows": 120,
"levels": [
{ "level": "B", "mapping": { "0": "property_9f2k7d", "1": "friendly_id" } }
]
}'
Response
{
"object": "import",
"id": "import_00a1b2c3d4e5f607",
"target": "pass_00a1b2c3d4e5f607",
"status": "pending",
"file": "file_9f2k7dq1w2e3r4t5",
"levels": [
{ "level": "B", "mapping": { "0": "property_9f2k7d", "1": "friendly_id" } }
],
"total_rows": 120,
"processed_rows": 0,
"failed_rows": 0,
"failed_validation_rows": 0,
"failed_infra_rows": 0,
"phases": [],
"started_at": null,
"finished_at": null,
"created_at": "2026-07-12T09:31:00Z"
}
List imports
Lists the team's imports, latest first. Returns a paginated list. Ontology-scoped API keys cannot use this team-wide list — fetch imports by id instead. Requires imports:read.
Optional attributes
- Name
created[gte]- Type
- string
- Description
Only imports created at or after this time. Unix seconds or an ISO date, inclusive of the whole second named.
- Name
created[lte]- Type
- string
- Description
Only imports created at or before this time. An inverted range is
400.
- Name
limit- Type
- integer
- Description
Page size, 1–100 (default 10).
- Name
cursor- Type
- string
- Description
A pagination cursor from a previous response's
next_cursor.
The creation window runs as a SQL WHERE rather than a scan, so pages stay exact. Filters join the cursor's scope; see pagination.
Every row here reports metadata: {} whatever the import actually
carries — see the import model. Fetch by id when you
need the real bag.
Request
curl -G https://api.synexcloud.com/v1/imports \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-d limit=10
Response
{
"object": "list",
"data": [
{
"object": "import",
"id": "import_00a1b2c3d4e5f607",
"target": "pass_00a1b2c3d4e5f607",
"status": "complete_with_errors",
"file": "file_9f2k7dq1w2e3r4t5",
"levels": [
{ "level": "B", "mapping": { "0": "property_9f2k7d", "1": "friendly_id" } }
],
"total_rows": 120,
"processed_rows": 120,
"failed_rows": 2,
"failed_validation_rows": 1,
"failed_infra_rows": 1,
"phases": [],
"started_at": "2026-07-12T09:31:05Z",
"finished_at": "2026-07-12T09:33:40Z",
"created_at": "2026-07-12T09:31:00Z"
}
],
"has_more": false,
"next_cursor": null,
"url": "/v1/imports"
}
Retrieve an import
Retrieves one import by id, with live progress counters. Poll it until status is complete, complete_with_errors, or failed. Requires imports:read.
Request
curl https://api.synexcloud.com/v1/imports/import_00a1b2c3d4e5f607 \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "import",
"id": "import_00a1b2c3d4e5f607",
"target": "pass_00a1b2c3d4e5f607",
"status": "importing",
"file": "file_9f2k7dq1w2e3r4t5",
"levels": [
{ "level": "B", "mapping": { "0": "property_9f2k7d", "1": "friendly_id" } }
],
"total_rows": 120,
"processed_rows": 74,
"failed_rows": 1,
"failed_validation_rows": 1,
"failed_infra_rows": 0,
"phases": [],
"started_at": "2026-07-12T09:31:05Z",
"finished_at": null,
"created_at": "2026-07-12T09:31:00Z"
}
List an import's rows
Lists an import's per-row outcomes, so you can see exactly which rows failed and why. Filter with status; passing status=failed selects both validation and infrastructure failures. The response also carries header (the column labels) and mapping for rendering rows back to their columns. Returns a paginated list. Requires imports:read.
Optional attributes
- Name
status- Type
- string
- Description
Filter by outcome —
failed(both failure kinds),done,failed_validation,failed_infra, orretrying.
- Name
cursor- Type
- string
- Description
A pagination cursor from a previous response's
next_cursor.
Request
curl -G https://api.synexcloud.com/v1/imports/import_00a1b2c3d4e5f607/rows \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-d status=failed
Response
{
"object": "list",
"data": [
{
"object": "import_row",
"id": "0#000017",
"status": "failed_validation",
"row": ["not-a-number", "unit-a17"],
"errors": [
{ "column": 0, "code": "invalid_measurement", "message": "Expected a number." }
],
"attempts": 1,
"passport": null
}
],
"header": ["Capacity", "Alias"],
"mapping": { "0": "property_9f2k7d", "1": "friendly_id" },
"has_more": false,
"next_cursor": null,
"url": "/v1/imports/import_00a1b2c3d4e5f607/rows"
}
Export failed rows
Returns the failed rows as text/csv, not JSON — the only operation in v1 that does. Requires imports:read. It is served as an attachment named {import_id}_failed_rows.csv.
The file holds both kinds of failure — rows rejected by validation and rows lost to an infrastructure error — one row each, laid out in the import's own column order, plus a trailing Errors column holding that row's failures as Property: message fragments joined by ; . You get the original cells and the reason side by side, which is the whole point: the sheet is repairable in place.
An import with no failed rows returns a header-only file — not a 404, and not an empty body. A consumer can treat "one line" as "nothing to fix" without special-casing an error status.
The byte-order mark
The file is UTF-8 with a BOM, deliberately. It exists to be opened in a spreadsheet, and Excel reads a BOM-less UTF-8 CSV as the local 8-bit codepage, mangling every non-ASCII character in it — an ontology with a °C unit or a supplier name carrying an accent comes out as mojibake for the very people the export is for.
The cost lands on programmatic consumers instead: strip the BOM before reading the first header cell, or your first column will not match by name. It is the single most common cause of a header lookup that mysteriously fails on column one only.
When to use it
If you are handling failures programmatically, use GET /v1/imports/{import}/rows instead — it returns the same rows as JSON, with no BOM and no CSV parsing in the way.
This endpoint exists because in practice the people who repair import failures work in a spreadsheet. Export the failures, fix the cells where they already live, then feed the corrections back through retry — the retry payload takes column index → corrected value, which maps directly onto the columns you just edited.
Request
curl https://api.synexcloud.com/v1/imports/import_8c2d5e1f9a4b7016/rows/export \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-o failed_rows.csv
# Strip the BOM on the way in
sed '1s/^\xEF\xBB\xBF//' failed_rows.csv > clean.csv
Response
Capacity,Friendly ID,Errors
not-a-number,U-1,Capacity: must be a number
4600,,Friendly ID: is required
Retry failed rows
Retries an import's failed rows, optionally with inline cell corrections. Supply rows — each entry pairs a row_id with a corrected_row that maps a column index to a fixed value — and/or scope: "infra_all" to re-run every infrastructure failure without listing them. Corrections are re-validated server-side first; rows that are still invalid come back in still_invalid with their errors and are not dispatched. Requires imports:write. Accepts an optional Idempotency-Key header.
Optional attributes
- Name
rows- Type
- array
- Description
Rows to retry. Each entry needs a
row_idand may carry acorrected_rowmapping column index → new cell value.
- Name
scope- Type
- string
- Description
Set to
infra_allto retry every infrastructure failure at once.
Request
curl https://api.synexcloud.com/v1/imports/import_00a1b2c3d4e5f607/retry \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rows": [
{ "row_id": "0#000017", "corrected_row": { "0": "4500" } }
]
}'
Response
{
"object": "import.retry",
"import": "import_00a1b2c3d4e5f607",
"dispatched": ["0#000017"],
"still_invalid": [],
"skipped": []
}