API
esc

Type to search.

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

  • idstring
    Crockford-base32 ULID (26 chars).
  • statusenum
    • validating
    • processing
    • completed
    • completed_with_errors
    • failed
  • modeenum
    • create
    • upsert
    • skip_existing
  • on_image_errorenum
    • skip
    • fail_row
  • countsobject
    Show child attributesHide child attributes
    • totalinteger, nullable
      Null while validating. Never an estimate.
    • pendinginteger
    • succeededinteger
    • partialinteger
      Rows whose product was created but one or more images could not be attached (on_image_error: skip).
    • failedinteger
  • results_urlstring (URL), nullable
    Once 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_attimestamp
    RFC 3339 / ISO 8601 timestamp.
  • started_attimestamp, nullable
    RFC 3339 / ISO 8601 timestamp.
  • finished_attimestamp, nullable
    RFC 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

  • lineinteger
    1-based line in the submitted file.
  • referencestring
  • statusenum
    • pending
    • succeeded
    • partial
    • failed
  • actionenum, nullable
    What happened to the product: created, updated (upsert hit), skipped (skip_existing hit), none (row failed). Null while pending.
    • created
    • updated
    • skipped
    • none
  • product_idstring, nullable
    The product this row created, updated or matched.
  • imagesarray of objects
    Show child attributesHide child attributes
    • indexinteger
      0-based position in the row's images array.
    • sourcestring
      The url or image_id you submitted for this image.
    • orientationstring
    • statusenum
      • attached
      • failed
    • image_idstring
      The image now attached to the product (present when status is attached).
    • errorobject
      Why the image was not attached. code is stable and grep-able: http_error, timeout, too_large, not_an_image, blocked_host, image_not_found, row_failed, …
      Show child attributesHide child attributes
      • codestring
      • messagestring
  • errorobject, nullable
    Set when the row failed as a whole: reference_already_exists, no_images, image_failed, too_many_attempts, …
    Show child attributesHide child attributes
    • codestring
    • messagestring

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

  • modeenumoptional
    • create
    • upsert
    • skip_existing
  • on_image_errorenumoptional
    • skip
    • fail_row
  • rowsarray of objectsrequired
    1..2000 rows. Split larger catalogues across several calls.
    Show child attributesHide child attributes
    • referencestringrequired
      Your product code. The key rows are matched on — required, and unique within the file and within your organization's API-created products.
    • namestringrequired
    • descriptionstringoptional
    • sexenumoptional
      • male
      • female
      • other
      • kid
    • imagesarray of objectsrequired
      Show child attributesHide child attributes
      • urlstring (URL)optional
        An https:// URL we can fetch without credentials for at least 24 hours (a presigned S3 URL, a CDN URL, …). Exactly one of url or image_id is required.
      • image_idstringoptional
        An image already uploaded through POST /v1/images. Exactly one of url or image_id is required.
      • orientationenumoptional
        Possible enum values (11)
        • front
        • back
        • left
        • right
        • top
        • bottom
        • three-quarter-left
        • three-quarter-right
        • detail
        • unlabeled
        • side

Also accepts application/x-ndjson.

Returns

Returns a Import object with status 202.

Errors
  • 400

    One or more rows are invalid; nothing was created.

  • 413

    Body too large for inline submission

  • 415

    Unsupported content type

  • 500

    Server error

  • 502

    Import 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_idstringrequired
    Crockford-base32 ULID (26 chars).

Returns

Returns a Import object with status 200.

Errors
  • 404

    Not 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_idstringrequired
    Crockford-base32 ULID (26 chars).
  • limitinteger· queryoptional

    Default: 25

  • cursorstring· queryoptional
  • statusenum· queryoptional
    • pending
    • succeeded
    • partial
    • failed

Returns

Returns a ImportRowList object with status 200.

Errors
  • 400

    Invalid cursor or filter

  • 404

    Import not found

  • 500

    Server 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
}