OBX Ingest API (write side) (0.1.0)

Download OpenAPI specification:

How a brand's own systems publish content to any OBX node.

Normative. The OBX Ingest API lets a brand's automation (PIM/ERP/DAM connector, spreadsheet export, the obx CLI, a GitHub Action, n8n/Make/Zapier) publish to ANY conforming node. It is keyed by the brand's own ids, so every write is an idempotent upsert: send the same thing twice and nothing changes (and no change entry is written).

See docs/ingest.md for the full semantics, including exactly how writes appear in the OBX Protocol /changes feed, and docs/import-format.md for the canonical CSV and image filename pattern.

Authentication: Authorization: Bearer <ingest token> (tokens start with obxi_), created in the node admin. Scopes: ingest:read, ingest:write, ingest:delete. A token MAY be restricted to some brands; writes outside them fail with 403 forbidden.

Errors are application/problem+json with obx_error (see docs/protocol.md#errors). Unknown fields in request bodies MUST be ignored by nodes (forward compatibility) and reported as a warning in the result.

Info

Token info, limits and rendition requirements.

Token info, limits and rendition requirements

Any scope. Clients SHOULD call this first; renditions.server_generated=false means the client must upload web/square/thumb itself.

Authorizations:
ingestToken

Responses

Response samples

Content type
application/json
{
  • "token": {
    },
  • "limits": {
    },
  • "renditions": {
    },
  • "accepted_content_types": [
    ],
  • "default_status": "published",
  • "brands": [
    ],
  • "languages": [
    ]
}

Catalog

Idempotent upserts of brands, products, variants and asset metadata, keyed by external id.

Read a brand

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

Responses

Response samples

Content type
application/json
{
  • "kind": "asset",
  • "id": "ast_1",
  • "external_id": "KO-1001_NVY_1",
  • "status": "published",
  • "visibility": "pending_files",
  • "etag": "\"c3d4\"",
  • "created_at": "2026-09-28T10:00:00Z",
  • "updated_at": "2026-09-28T10:00:00Z",
  • "data": {
    },
  • "missing_files": [
    ]
}

Create or replace a brand

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

header Parameters
If-Match
string

Optional optimistic concurrency with the record's ETag.

Request Body schema: application/json
required
name
required
string [ 1 .. 200 ] characters
Url (string) or null
Url (string) or null
LocalizedText (object) or null
status
string (PublicationStatus)
Enum: "published" "unpublished"

Brand-side publication state. Only 'published' objects (with published parents) are visible through the OBX Protocol.

^x-[a-z0-9]+-[a-z0-9_-]+$
pattern property
any

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Partially update a brand (JSON merge patch)

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

header Parameters
If-Match
string

Optional optimistic concurrency with the record's ETag.

Request Body schema: application/merge-patch+json
required
object

Responses

Request samples

Content type
application/merge-patch+json
{ }

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Delete a brand

Requires scope ingest:delete. Fails with 409 conflict while the brand still has products (delete or move them first). Idempotent: deleting an unknown id returns 200 with result unchanged.

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

Responses

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Read a product

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

Responses

Response samples

Content type
application/json
{
  • "kind": "asset",
  • "id": "ast_1",
  • "external_id": "KO-1001_NVY_1",
  • "status": "published",
  • "visibility": "pending_files",
  • "etag": "\"c3d4\"",
  • "created_at": "2026-09-28T10:00:00Z",
  • "updated_at": "2026-09-28T10:00:00Z",
  • "data": {
    },
  • "missing_files": [
    ]
}

Create or replace a product

Full replacement: omitted optional fields become null. Exception: an omitted status keeps the current status (new objects get the node's default status, normally published), so a nightly sync never re-publishes what someone unpublished by hand unless it sends status explicitly.

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

header Parameters
If-Match
string

Optional optimistic concurrency with the record's ETag.

Request Body schema: application/json
required
ExternalId (string) or null
style_number
required
string [ 1 .. 200 ] characters
required
object (LocalizedText) non-empty

Free text per language, keyed by BCP 47 tag. At least one language. Example: {"nl": "Trui", "en": "Sweater"}.

LocalizedText (object) or null
item_type
string or null^[a-z][a-z0-9_]{0,63}$
gpc_brick
string or null^[0-9]{8}$
target_group
string or null^[a-z][a-z0-9_]{0,63}$
season
string or null <= 64 characters
collection
string or null <= 200 characters
Array of Materials (objects) or null
Array of Care (objects) or null
CountryCode (string) or null
Url (string) or null
object or null
status
string (PublicationStatus)
Enum: "published" "unpublished"

Brand-side publication state. Only 'published' objects (with published parents) are visible through the OBX Protocol.

^x-[a-z0-9]+-[a-z0-9_-]+$
pattern property
any

Responses

Request samples

Content type
application/json
{
  • "style_number": "KO-1007",
  • "name": {
    }
}

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Partially update a product (JSON merge patch), e.g. to unpublish

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

header Parameters
If-Match
string

Optional optimistic concurrency with the record's ETag.

Request Body schema: application/merge-patch+json
required
object

Responses

Request samples

Content type
application/merge-patch+json
{ }

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Delete a product with its variants and assets

Requires ingest:delete. Emits tombstones for the product and each of its visible variants and assets. Idempotent.

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

Responses

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Set the image order of one product colour atomically

Assigns positions 1..n to the listed assets (all non-deleted assets of this product and color_code MUST be listed; 422 validation_failed otherwise). This is how you swap or reorder images without position_conflict. Emits one asset upsert per asset whose position changed.

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

Request Body schema: application/json
required
color_code
required
string or null

null = the product-wide (all colours) assets.

asset_external_ids
required
Array of strings (ExternalId) [ 1 .. 999 ] items unique [ items [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$ ]

Responses

Request samples

Content type
application/json
{
  • "color_code": "string",
  • "asset_external_ids": [
    ]
}

Response samples

Content type
application/json
[
  • {
    }
]

Read a variant

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

Responses

Response samples

Content type
application/json
{
  • "kind": "asset",
  • "id": "ast_1",
  • "external_id": "KO-1001_NVY_1",
  • "status": "published",
  • "visibility": "pending_files",
  • "etag": "\"c3d4\"",
  • "created_at": "2026-09-28T10:00:00Z",
  • "updated_at": "2026-09-28T10:00:00Z",
  • "data": {
    },
  • "missing_files": [
    ]
}

Create or replace a variant

The product (product_external_id) MUST exist, else 422 unknown_parent. GTIN check digit MUST be valid (invalid_gtin) and unique in the node (409 gtin_conflict).

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

header Parameters
If-Match
string

Optional optimistic concurrency with the record's ETag.

Request Body schema: application/json
required
product_external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$

The brand's own identifier for an object (from its PIM/ERP/spreadsheet). 1-200 characters, no control characters and no '/'. Leading/trailing whitespace is trimmed by nodes. Compared case-sensitively.

GtinInput (string) or null
sku
string or null <= 200 characters
required
object (Color)
size
string or null <= 64 characters
size_system
string or null <= 16 characters
status
string (PublicationStatus)
Enum: "published" "unpublished"

Brand-side publication state. Only 'published' objects (with published parents) are visible through the OBX Protocol.

^x-[a-z0-9]+-[a-z0-9_-]+$
pattern property
any

Responses

Request samples

Content type
application/json
{
  • "product_external_id": "KO-1007",
  • "gtin": "2000100701010",
  • "color": {
    },
  • "size": "M",
  • "size_system": "INT"
}

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Partially update a variant (JSON merge patch)

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

header Parameters
If-Match
string

Optional optimistic concurrency with the record's ETag.

Request Body schema: application/merge-patch+json
required
object

Responses

Request samples

Content type
application/merge-patch+json
{ }

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Delete a variant

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

Responses

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Read asset metadata (including missing_files)

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

Responses

Response samples

Content type
application/json
{
  • "kind": "asset",
  • "id": "ast_1",
  • "external_id": "KO-1001_NVY_1",
  • "status": "published",
  • "visibility": "pending_files",
  • "etag": "\"c3d4\"",
  • "created_at": "2026-09-28T10:00:00Z",
  • "updated_at": "2026-09-28T10:00:00Z",
  • "data": {
    },
  • "missing_files": [
    ]
}

Create or replace asset metadata

The product MUST exist (unknown_parent). position MUST be free among the product's other non-deleted assets of the same color_code (409 position_conflict; replacing an image = reuse its external_id). files attaches blobs uploaded earlier (422 blob_missing if a sha256 is unknown); this is the atomic way to replace an original together with client-generated renditions. The asset becomes visible once its original and all required renditions exist (visibility: pending_files until then).

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

header Parameters
If-Match
string

Optional optimistic concurrency with the record's ETag.

Request Body schema: application/json
required
product_external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$

The brand's own identifier for an object (from its PIM/ERP/spreadsheet). 1-200 characters, no control characters and no '/'. Leading/trailing whitespace is trimmed by nodes. Compared case-sensitively.

color_code
string or null <= 64 characters
variant_external_ids
Array of strings or null (ExternalId) [ items [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$ ]
media_type
string
Value: "image"
role
required
string (Role)
Enum: "packshot" "model" "detail" "ghost" "flatlay" "scene" "swatch" "size_chart" "other"

What the image shows. Stable key; labels are presentation.

View (string) or null
position
required
integer [ 1 .. 999 ]
filename
string or null <= 255 characters

Download filename; default <external_id>.<ext>.

LocalizedText (object) or null
object or null

Partial Rights; omitted fields take the node's default rights (Settings). null = all defaults.

object or null

Omitted fields default to ai_generated=false, ai_edited=false, source=photo (generated when ai_generated), c2pa=none (nodes MAY detect embedded C2PA).

object or null

Attach previously uploaded blobs atomically. Omit to keep current files (or upload via PUT .../files/{rendition}).

status
string (PublicationStatus)
Enum: "published" "unpublished"

Brand-side publication state. Only 'published' objects (with published parents) are visible through the OBX Protocol.

^x-[a-z0-9]+-[a-z0-9_-]+$
pattern property
any

Responses

Request samples

Content type
application/json
{
  • "product_external_id": "KO-1007",
  • "color_code": "SND",
  • "role": "packshot",
  • "view": "front",
  • "position": 1,
  • "rights": {
    },
  • "provenance": {
    },
  • "files": {
    }
}

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Partially update asset metadata (JSON merge patch)

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

header Parameters
If-Match
string

Optional optimistic concurrency with the record's ETag.

Request Body schema: application/merge-patch+json
required
object

Responses

Request samples

Content type
application/merge-patch+json
{ }

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Delete an asset

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

Responses

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Files

Binary uploads with sha256 dedupe, including resumable multipart uploads.

Upload the original or a client-generated rendition

Raw body, Content-Type of the file. OBX-Content-SHA256 is RECOMMENDED; when present the node verifies it (422 checksum_mismatch). The asset metadata MUST exist (404). Uploading the same bytes again is a no-op (unchanged).

Replacing original on a node without server-side renditions: the node keeps serving the previous original and renditions until all required renditions uploaded AFTER the new original are present, then swaps atomically (one asset upsert in /changes). Renditions MUST follow the rendition rules (web/thumb: JPEG, longest side <= 2000/600, never upscaled; square: web on a white square canvas); nodes MAY reject (422 validation_failed) renditions whose size violates them.

Authorizations:
ingestToken
path Parameters
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$
Examples: KO-1001

The brand's own id, percent-encoded.

rendition
required
string (RenditionName)
Enum: "original" "web" "square" "thumb"

Rendition name.

header Parameters
OBX-Content-SHA256
string (Sha256) ^[a-f0-9]{64}$

Lowercase hex sha256 of the body; verified when present.

Request Body schema: image/*
required
string <application/octet-stream>

Responses

Response samples

Content type
application/json
{
  • "index": 0,
  • "op": "upsert",
  • "kind": "product",
  • "external_id": "KO-1001",
  • "result": "unchanged",
  • "id": "prd_1",
  • "visibility": "visible",
  • "etag": "\"a1b2\"",
  • "error": null,
  • "warnings": [ ]
}

Does the node already have these bytes?

Dedupe check before uploading. 200 = present (skip the upload), 404 = upload it.

Authorizations:
ingestToken
path Parameters
sha256
required
string (Sha256) ^[a-f0-9]{64}$

Lowercase hex SHA-256 of the exact bytes.

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 400,
  • "detail": "string",
  • "instance": "string",
  • "obx_error": "invalid_request",
  • "errors": [
    ],
  • "retry_after": 0
}

Upload bytes (content-addressed)

Raw body. The node verifies the sha256 (422 checksum_mismatch). Blobs not referenced by any asset MAY be garbage-collected after 24 h. Bodies larger than limits.max_upload_bytes use /uploads.

Authorizations:
ingestToken
path Parameters
sha256
required
string (Sha256) ^[a-f0-9]{64}$

Lowercase hex SHA-256 of the exact bytes.

Request Body schema: image/*
required
string <application/octet-stream>

Responses

Response samples

Content type
application/json
{
  • "sha256": "3333333333333333333333333333333333333333333333333333333333333333",
  • "bytes": 48211,
  • "content_type": "image/jpeg",
  • "width": 1200,
  • "height": 1500,
  • "created": true
}

Start a resumable multipart upload

For files above max_upload_bytes. If the node already has the sha256 it answers 200 with the Blob and no upload is needed.

Authorizations:
ingestToken
Request Body schema: application/json
required
sha256
required
string (Sha256) ^[a-f0-9]{64}$

Lowercase hex SHA-256 of the exact bytes.

bytes
required
integer >= 1
content_type
required
string

Responses

Request samples

Content type
application/json
{
  • "sha256": "string",
  • "bytes": 1,
  • "content_type": "string"
}

Response samples

Content type
application/json
{
  • "sha256": "3333333333333333333333333333333333333333333333333333333333333333",
  • "bytes": 48211,
  • "content_type": "image/jpeg",
  • "width": 1200,
  • "height": 1500,
  • "created": true
}

Upload status (which parts arrived)

Authorizations:
ingestToken
path Parameters
id
required
string (Id) ^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$
Examples: prd_01J9ZK3Q7T5W

Opaque, node-assigned identifier. Stable for the life of the object. Consumers MUST treat it as opaque.

Responses

Response samples

Content type
application/json
{
  • "id": "upl_1",
  • "sha256": "4444444444444444444444444444444444444444444444444444444444444444",
  • "bytes": 83886080,
  • "content_type": "image/tiff",
  • "part_bytes": 8388608,
  • "parts_total": 10,
  • "received_parts": [
    ],
  • "status": "open",
  • "expires_at": "2026-09-29T10:00:00Z"
}

Abort an upload

Authorizations:
ingestToken
path Parameters
id
required
string (Id) ^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$
Examples: prd_01J9ZK3Q7T5W

Opaque, node-assigned identifier. Stable for the life of the object. Consumers MUST treat it as opaque.

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 400,
  • "detail": "string",
  • "instance": "string",
  • "obx_error": "invalid_request",
  • "errors": [
    ],
  • "retry_after": 0
}

Upload one part

Idempotent per part number; re-sending a part replaces it. Every part except the last MUST be exactly part_bytes.

Authorizations:
ingestToken
path Parameters
id
required
string (Id) ^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$
Examples: prd_01J9ZK3Q7T5W

Opaque, node-assigned identifier. Stable for the life of the object. Consumers MUST treat it as opaque.

number
required
integer >= 1
Request Body schema: application/octet-stream
required
string <application/octet-stream>

Responses

Response samples

Content type
application/json
{
  • "id": "upl_1",
  • "sha256": "4444444444444444444444444444444444444444444444444444444444444444",
  • "bytes": 83886080,
  • "content_type": "image/tiff",
  • "part_bytes": 8388608,
  • "parts_total": 10,
  • "received_parts": [
    ],
  • "status": "open",
  • "expires_at": "2026-09-29T10:00:00Z"
}

Assemble the parts into a blob

Verifies size and sha256. Missing parts give 409 upload_incomplete; a hash mismatch gives 422 checksum_mismatch (the upload is aborted).

Authorizations:
ingestToken
path Parameters
id
required
string (Id) ^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$
Examples: prd_01J9ZK3Q7T5W

Opaque, node-assigned identifier. Stable for the life of the object. Consumers MUST treat it as opaque.

Responses

Response samples

Content type
application/json
{
  • "sha256": "3333333333333333333333333333333333333333333333333333333333333333",
  • "bytes": 48211,
  • "content_type": "image/jpeg",
  • "width": 1200,
  • "height": 1500,
  • "created": true
}

Batch

Many operations in one request.

Many operations in one request

Up to limits.max_batch_operations (>= 1000) operations, processed in order, each one atomic on its own; a failing operation does not stop the rest. Body: a JSON array of IngestOperation, or NDJSON (one operation per line). Binary data is referenced by sha256 (upload blobs first). The node answers 200 with all results when it processed the batch within the request, or 202 with a job (always when the client sends Prefer: respond-async). Clients MUST handle both.

Authorizations:
ingestToken
header Parameters
Idempotency-Key
string <= 200 characters

Repeating a request with the same key within 24 h returns the original response (same job).

Prefer
string
Value: "respond-async"
Request Body schema:
required
Array (<= 1000 items)
op
required
string
Enum: "upsert" "patch" "delete" "publish" "unpublish"

upsert = PUT (create or replace); patch = JSON merge patch; publish/unpublish = patch of status.

kind
required
string
Enum: "brand" "product" "variant" "asset"
external_id
required
string (ExternalId) [ 1 .. 200 ] characters ^[^\x00-\x1F\x7F/]+$

The brand's own identifier for an object (from its PIM/ERP/spreadsheet). 1-200 characters, no control characters and no '/'. Leading/trailing whitespace is trimmed by nodes. Compared case-sensitively.

data
object

upsert: BrandInput|ProductInput|VariantInput|AssetInput by kind. patch: merge patch of that. Absent for delete/publish/unpublish.

if_match
string or null

Optional ETag precondition.

^x-[a-z0-9]+-[a-z0-9_-]+$
pattern property
any

Responses

Request samples

Content type
[
  • {
    }
]

Response samples

Content type
application/json
{
  • "results": [
    ],
  • "summary": {
    }
}

Imports

Canonical CSV plus images as one job.

Import a canonical CSV and/or images as one job

multipart/form-data with any of: package (zip with products.csv, optional assets.csv and images), csv (products.csv), assets_csv (assets.csv), images (one zip, or repeated image files), images_manifest (JSON: images already uploaded as blobs, referenced by file name + sha256, optionally with client-generated renditions) and options (JSON). The manifest is how the obx CLI and the admin UI avoid re-uploading unchanged images: HEAD /blobs/{sha256}, upload only what is missing, then import with the manifest. Always asynchronous: 202 + job. Row and file problems are reported as ImportRowError results, never as a failed request. See docs/import-format.md.

Authorizations:
ingestToken
header Parameters
Idempotency-Key
string <= 200 characters

Repeating a request with the same key within 24 h returns the original response (same job).

Request Body schema: multipart/form-data
required
package
string <application/zip>
csv
string <text/csv>
assets_csv
string <text/csv>
images
Array of strings
Array of objects (ImagesManifest) <= 100000 items

Images already uploaded as blobs. file is the image file name the canonical filename pattern and assets.csv refer to. Unknown sha256 -> row error blob_missing.

object (ImportOptions)

Responses

Response samples

Content type
application/json
{
  • "id": "job_1",
  • "kind": "import",
  • "status": "succeeded",
  • "created_at": "2026-09-28T10:00:00Z",
  • "finished_at": "2026-09-28T10:00:00Z",
  • "progress": {
    },
  • "summary": {
    },
  • "import_run": {
    },
}

Jobs

Status of asynchronous work.

Recent jobs of this token's brand node

Authorizations:
ingestToken
query Parameters
status
string
Enum: "queued" "running" "succeeded" "failed"
cursor
string (Cursor) [ 1 .. 4096 ] characters

Opaque pagination or feed cursor. Clients MUST NOT parse or construct it.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "next_cursor": "string"
}

Job status

Authorizations:
ingestToken
path Parameters
id
required
string (Id) ^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$
Examples: prd_01J9ZK3Q7T5W

Opaque, node-assigned identifier. Stable for the life of the object. Consumers MUST treat it as opaque.

Responses

Response samples

Content type
application/json
{
  • "id": "job_1",
  • "kind": "import",
  • "status": "succeeded",
  • "created_at": "2026-09-28T10:00:00Z",
  • "finished_at": "2026-09-28T10:00:00Z",
  • "progress": {
    },
  • "summary": {
    },
  • "import_run": {
    },
}

Per-operation (batch) or per-row (import) results

Authorizations:
ingestToken
path Parameters
id
required
string (Id) ^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$
Examples: prd_01J9ZK3Q7T5W

Opaque, node-assigned identifier. Stable for the life of the object. Consumers MUST treat it as opaque.

query Parameters
errors_only
boolean
Default: false
cursor
string (Cursor) [ 1 .. 4096 ] characters

Opaque pagination or feed cursor. Clients MUST NOT parse or construct it.

limit
integer [ 1 .. 1000 ]
Default: 100

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "next_cursor": "string"
}