API reference
Imports
Bulk product ingestion, one row per SKU with the URLs of its photos. Validated whole, then processed as a job whose rows succeed or fail independently.
The Import object
Attributes
idstringCrockford-base32 ULID (26 chars).statusenumvalidatingprocessingcompletedcompleted_with_errorsfailed
modeenumcreateupsertskip_existing
on_image_errorenumskipfail_row
countsobjectShow child attributesHide child attributes
totalinteger, nullableNull whilevalidating. Never an estimate.pendingintegersucceededintegerpartialintegerRows whose product was created but one or more images could not be attached (on_image_error: skip).failedinteger
results_urlstring (URL), nullableOnce terminal: a short-lived signed URL to a JSONL file with one line per input row, in input order. Re-signed on every read; do not cache.created_attimestampRFC 3339 / ISO 8601 timestamp.started_attimestamp, nullableRFC 3339 / ISO 8601 timestamp.finished_attimestamp, nullableRFC 3339 / ISO 8601 timestamp.
The Import object
{
"id": "01HX5K2MZ7A3Q4FBNDC0EVDXIM",
"status": "validating",
"mode": "create",
"on_image_error": "skip",
"counts": {
"total": 1,
"pending": 1,
"succeeded": 1,
"partial": 1,
"failed": 1
},
"results_url": "https://example.com/…",
"created_at": "2026-04-27T14:32:00Z",
"started_at": "2026-04-27T14:32:00Z",
"finished_at": "2026-04-27T14:32:00Z"
}The ImportRowResult object
One input row and what became of it. The results file (results_url) is these objects, one per line, in input order.
Attributes
lineinteger1-based line in the submitted file.referencestringstatusenumpendingsucceededpartialfailed
actionenum, nullableWhat happened to the product:created,updated(upsert hit),skipped(skip_existing hit),none(row failed). Null while pending.createdupdatedskippednone
product_idstring, nullableThe product this row created, updated or matched.imagesarray of objectsShow child attributesHide child attributes
indexinteger0-based position in the row'simagesarray.sourcestringTheurlorimage_idyou submitted for this image.orientationstringstatusenumattachedfailed
image_idstringThe image now attached to the product (present whenstatusisattached).errorobjectWhy the image was not attached.codeis stable and grep-able:http_error,timeout,too_large,not_an_image,blocked_host,image_not_found,row_failed, …Show child attributesHide child attributes
codestringmessagestring
errorobject, nullableSet when the row failed as a whole:reference_already_exists,no_images,image_failed,too_many_attempts, …Show child attributesHide child attributes
codestringmessagestring
The ImportRowResult object
{
"line": 1,
"reference": "TEE-SAGE-001",
"status": "pending",
"action": "created",
"product_id": "01HX5K2MZ7A3Q4FBNDC0EVDXY1",
"images": [
{
"index": 0,
"source": "string",
"orientation": "string",
"status": "attached",
"image_id": "01HX5K2MZ7A3Q4FBNDC0EVDXY1",
"error": {
"code": "string",
"message": "string"
}
}
],
"error": {
"code": "string",
"message": "string"
}
}Create an import
POST/v1/imports
Submit up to 2000 products in one call, one row per SKU, each with the URLs of its photos. Returns 202 with a job you can poll (GET /v1/imports/{id}) or subscribe to (import.completed / import.failed webhooks).
Two body formats. application/x-ndjson — one JSON object per line, options as query parameters (?mode=upsert&on_image_error=skip). Or application/json — { "mode", "on_image_error", "rows": [...] }.
curl https://api.veeton.com/v1/imports?mode=upsert \
-H "Authorization: Bearer $VEETON_KEY" \
-H "Content-Type: application/x-ndjson" \
--data-binary @catalogue.jsonl
Validation is all-or-nothing. Every row is checked before anything is created; if any row is invalid the whole submission is refused with 400 and every problem listed by line number, so you fix the file once and resubmit. Duplicate references within one file are refused.
Rows are then independent. One row failing to import never affects another. Per-row outcomes are available once the job finishes.
Retries. Send Idempotency-Key so a retried submission returns the same job instead of creating a second one. Within the job, mode: upsert (the default) matches rows to products you previously created through the API by reference, so resubmitting a file never duplicates your catalogue.
Parameters
modeenumoptionalcreateupsertskip_existing
on_image_errorenumoptionalskipfail_row
rowsarray of objectsrequired1..2000 rows. Split larger catalogues across several calls.Show child attributesHide child attributes
referencestringrequiredYour product code. The key rows are matched on — required, and unique within the file and within your organization's API-created products.namestringrequireddescriptionstringoptionalsexenumoptionalmalefemaleotherkid
imagesarray of objectsrequiredShow child attributesHide child attributes
urlstring (URL)optionalAnhttps://URL we can fetch without credentials for at least 24 hours (a presigned S3 URL, a CDN URL, …). Exactly one ofurlorimage_idis required.image_idstringoptionalAn image already uploaded throughPOST /v1/images. Exactly one ofurlorimage_idis required.orientationenumoptionalPossible enum values (11)
frontbackleftrighttopbottomthree-quarter-leftthree-quarter-rightdetailunlabeledside
Returns
Returns a Import object with status 202.
Errors
400One or more rows are invalid; nothing was created.
413Body too large for inline submission
415Unsupported content type
500Server error
502Import pipeline unavailable; the job was marked failed
POST/v1/imports
curl https://api.veeton.com/v1/imports \
-H "Authorization: Bearer $VEETON_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "create",
"on_image_error": "skip",
"rows": [
{
"reference": "TEE-SAGE-001",
"name": "Crewneck Tee — Sage",
"description": "A short description.",
"sex": "male",
"images": [
{
"url": "https://example.com/…",
"image_id": "01HX5K2MZ7A3Q4FBNDC0EVDXY1",
"orientation": "front"
}
]
}
]
}'Response · 202
{
"id": "01HX5K2MZ7A3Q4FBNDC0EVDXIM",
"status": "validating",
"mode": "create",
"on_image_error": "skip",
"counts": {
"total": 1,
"pending": 1,
"succeeded": 1,
"partial": 1,
"failed": 1
},
"results_url": "https://example.com/…",
"created_at": "2026-04-27T14:32:00Z",
"started_at": "2026-04-27T14:32:00Z",
"finished_at": "2026-04-27T14:32:00Z"
}Retrieve an import
GET/v1/imports/{import_id}
Status and per-outcome counters. Safe to poll: the counters are maintained by the processor, so a read costs one indexed lookup regardless of how many rows the job has.
While the job is validating or processing the response carries Retry-After; wait that long between polls. A 429 on this endpoint is a rate-limit signal, not a job failure — nothing about the job changed.
Parameters
import_idstringrequiredCrockford-base32 ULID (26 chars).
Returns
Returns a Import object with status 200.
Errors
404Not found
GET/v1/imports/{import_id}
curl https://api.veeton.com/v1/imports/01HX5K2MZ7A3Q4FBNDC0EVDXY1 \
-H "Authorization: Bearer $VEETON_KEY"Response · 200
{
"id": "01HX5K2MZ7A3Q4FBNDC0EVDXIM",
"status": "validating",
"mode": "create",
"on_image_error": "skip",
"counts": {
"total": 1,
"pending": 1,
"succeeded": 1,
"partial": 1,
"failed": 1
},
"results_url": "https://example.com/…",
"created_at": "2026-04-27T14:32:00Z",
"started_at": "2026-04-27T14:32:00Z",
"finished_at": "2026-04-27T14:32:00Z"
}List import rows
GET/v1/imports/{import_id}/rows
One entry per submitted row, in input order, with what became of it: the product it created or updated, the outcome of each image, and the error if it failed. Rows not yet processed read as pending.
Filter with status=failed to drive a fix-and-resubmit loop without paging through successes. The same objects, one per line, are what results_url on the import points at once the job is terminal — use the file for a whole-catalogue reconciliation, this endpoint for programmatic loops.
Parameters
import_idstringrequiredCrockford-base32 ULID (26 chars).limitinteger· queryoptionalcursorstring· queryoptionalstatusenum· queryoptionalpendingsucceededpartialfailed
Returns
Returns a ImportRowList object with status 200.
Errors
400Invalid cursor or filter
404Import not found
500Server error
GET/v1/imports/{import_id}/rows
curl https://api.veeton.com/v1/imports/01HX5K2MZ7A3Q4FBNDC0EVDXY1/rows \
-H "Authorization: Bearer $VEETON_KEY"Response · 200
{
"data": [
{
"line": 1,
"reference": "TEE-SAGE-001",
"status": "pending",
"action": "created",
"product_id": "01HX5K2MZ7A3Q4FBNDC0EVDXY1",
"images": [
{
"index": 0,
"source": "string",
"orientation": "string",
"status": "attached",
"image_id": "01HX5K2MZ7A3Q4FBNDC0EVDXY1",
"error": {
"code": "string",
"message": "string"
}
}
],
"error": {
"code": "string",
"message": "string"
}
}
],
"next_cursor": "eyJpZCI6InByb2RfMDFIWCJ9",
"has_more": true
}