API
esc

Type to search.

API reference

Imports

Bulk product ingestion, one row per product 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
  • urlstring (URL), nullable
    Once terminal: a signed URL to the results file — JSONL, one ImportRowResult per line, in input order. Re-signed on every read; cache the file, not the URL.
  • url_expires_attimestamp, nullable
    RFC 3339 / ISO 8601 timestamp.
  • 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
  },
  "url": "https://example.com/…",
  "url_expires_at": "2026-04-27T14:32:00Z",
  "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 (url on the import) 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
      Show child attributesHide child attributes
      • codestring
      • messagestring
      • paramstring
  • errorobject, nullable
    Show child attributesHide child attributes
    • codestring
    • messagestring
    • paramstring

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": "product_not_found",
        "message": "Product 01HX...XY1 was not found.",
        "param": "inputs.product_id"
      }
    }
  ],
  "error": {
    "code": "product_not_found",
    "message": "Product 01HX...XY1 was not found.",
    "param": "inputs.product_id"
  }
}

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 (10)
        • front
        • back
        • left
        • right
        • top
        • bottom
        • three-quarter-left
        • three-quarter-right
        • detail
        • unlabeled

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
  },
  "url": "https://example.com/…",
  "url_expires_at": "2026-04-27T14:32:00Z",
  "created_at": "2026-04-27T14:32:00Z",
  "started_at": "2026-04-27T14:32:00Z",
  "finished_at": "2026-04-27T14:32:00Z"
}

List imports

GET/v1/imports

Your import jobs, newest first, each with its counters and, once terminal, a signed url to its results file. Filter by status (the shared job vocabulary; validating counts as processing, completed_with_errors as completed).

Parameters

  • limitinteger· queryoptional

    Default : 25

  • cursorstring· queryoptional
  • statusenum· queryoptional
    • queued
    • processing
    • completed
    • failed

Returns

Returns a ImportList object with status 200.

Errors
  • 400

    Invalid cursor

  • 500

    Server error

GET/v1/imports

curl https://api.veeton.com/v1/imports \
  -H "Authorization: Bearer $VEETON_KEY"

Response · 200

{
  "data": [
    {
      "id": "01HX5K2MZ7A3Q4FBNDC0EVDXIM",
      "status": "validating",
      "mode": "create",
      "on_image_error": "skip",
      "counts": {
        "total": 1,
        "pending": 1,
        "succeeded": 1,
        "partial": 1,
        "failed": 1
      },
      "url": "https://example.com/…",
      "url_expires_at": "2026-04-27T14:32:00Z",
      "created_at": "2026-04-27T14:32:00Z",
      "started_at": "2026-04-27T14:32:00Z",
      "finished_at": "2026-04-27T14:32:00Z"
    }
  ],
  "next_cursor": "eyJpZCI6InByb2RfMDFIWCJ9",
  "has_more": true
}

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
  },
  "url": "https://example.com/…",
  "url_expires_at": "2026-04-27T14:32:00Z",
  "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 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": "product_not_found",
            "message": "Product 01HX...XY1 was not found.",
            "param": "inputs.product_id"
          }
        }
      ],
      "error": {
        "code": "product_not_found",
        "message": "Product 01HX...XY1 was not found.",
        "param": "inputs.product_id"
      }
    }
  ],
  "next_cursor": "eyJpZCI6InByb2RfMDFIWCJ9",
  "has_more": true
}