Download OpenAPI specification:
How retailers' software discovers a brand node, gets access and pulls product content.
Normative. The Open Brand Exchange (OBX) Protocol is the read side of a brand node: a retailer's software (shop, PIM, ERP, content hub) discovers the node, requests access, authenticates with OAuth2 client credentials and pulls brands, products, variants (with GTIN) and final images with role, view, order, rights and AI provenance.
The prose specification in docs/protocol.md is authoritative where this file is silent.
Key words MUST, SHOULD, MAY are to be interpreted as described in RFC 2119.
Conventions for every endpoint:
x-<vendor>- (for example x-acme-crop).application/problem+json (RFC 9457) with an obx_error code, except the
OAuth2 token endpoint which uses RFC 6749 error JSON.OBX-Version: 0.1 and rate limit headers (RateLimit-Limit,
RateLimit-Remaining, RateLimit-Reset); 429 carries Retry-After.Public. MUST be served with Access-Control-Allow-Origin: * and SHOULD be cacheable for 5 minutes. It is the only endpoint with CORS headers; all others send none (security.md section 4).
{- "obx_version": "0.1",
- "spec_version": "0.1.0",
- "node": {
- "software": "obx-node-go",
- "version": "0.1.0"
}, - "publisher": {
- "id": "pub_kestrel",
- "name": "Kestrel Outfitters",
- "accent_color": "#1F4E5F",
- "welcome_text": {
- "en": "Welcome, retail partners.",
- "nl": "Welkom, retailpartners."
}
}, - "auth": {
- "scopes_supported": [
- "catalog:read",
- "media:read",
- "media:original",
- "usage:write"
], - "token_endpoint_auth_methods_supported": [
- "client_secret_basic",
- "client_secret_post"
]
}, - "capabilities": [
- "changes",
- "products",
- "by_gtin",
- "renditions",
- "exports",
- "webhooks",
- "usage",
- "access_requests",
- "ingest",
- "ingest.server_renditions",
- "portal"
], - "brands": [
- {
- "id": "brd_kestrel",
- "name": "Kestrel",
- "logo_url": null
}, - {
- "id": "brd_trail",
- "name": "Kestrel Trail",
- "logo_url": null
}
], - "languages": [
- "nl",
- "en"
], - "limits": {
- "changes_page_max": 500,
- "products_page_max": 100,
- "gtin_lookup_max": 2000,
- "tombstone_retention_days": 90
}
}Creates a retailer access request that the brand approves or rejects in its admin.
Unauthenticated and rate limited (RECOMMENDED: 5 per hour per IP). The response is the
same whether or not the brand already knows the retailer (no enumeration). Nodes whose
brand disabled requests answer 403 forbidden.
required | object |
required | object |
| access | Array of strings non-empty unique Items Enum: "portal" "api" What is requested: portal account and/or API client credentials. Default ["portal"]. |
| requested_brand_ids | Array of strings or null (Id) [ items^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$ ] Brands of interest; null = all. |
| message | string or null <= 2000 characters |
| ^x-[a-z0-9]+-[a-z0-9_-]+$ pattern property | any |
{- "retailer": {
- "name": "Example Store",
- "reference": "C-1002",
- "vat_number": null,
- "country": "NL"
}, - "access": [
- "portal",
- "api"
], - "requested_brand_ids": null,
- "message": "We stock Kestrel since 2024."
}{- "id": "acr_1",
- "status": "pending",
- "created_at": "2026-09-28T10:00:00Z",
- "decided_at": null,
- "reason": null,
- "request_token": "obxr_4f1c2b9e8d7a6c5b4a39281706f5e4d3",
}Authenticated with the request_token from the 202 response as Bearer token.
After approval with API access the response contains credentials with the
client_secret exactly once (first read); later reads return client_secret: null
and claimed: true. Poll at most once per minute.
| 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. |
{- "id": "acr_1",
- "status": "pending",
- "created_at": "2026-09-28T10:00:00Z",
- "decided_at": null,
- "reason": null,
- "request_token": "obxr_4f1c2b9e8d7a6c5b4a39281706f5e4d3",
}RFC 6749 section 4.4. Nodes MUST support client_secret_basic and MAY support
client_secret_post. scope is optional; when absent all scopes of the client are
granted. Requesting a scope the client does not have yields invalid_scope.
A revoked client or blocked retailer yields invalid_client. Access tokens SHOULD
live 3600 s; there are no refresh tokens (request a new token).
| grant_type required | string Value: "client_credentials" |
| scope | string Space-separated subset of catalog:read media:read media:original usage:write. |
| client_id | string Only for client_secret_post. |
| client_secret | string Only for client_secret_post. |
{- "access_token": "obxa_ZXhhbXBsZS1hY2Nlc3MtdG9rZW4",
- "token_type": "Bearer",
- "expires_in": 3600,
- "scope": "catalog:read media:read"
}{- "id": "grt_3",
- "retailer": {
- "id": "rtl_3",
- "name": "Example Store"
}, - "brand_ids": [
- "brd_kestrel"
], - "scopes": [
- "catalog:read",
- "media:read",
- "usage:write"
], - "original_allowed": false,
- "status": "active",
- "updated_at": "2026-09-28T10:00:00Z"
}{- "items": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "name": "string",
- "description": { },
- "updated_at": "2026-09-28T10:00:00Z"
}
]
}| 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. |
{- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "name": "string",
- "description": { },
- "updated_at": "2026-09-28T10:00:00Z"
}Keyset-paginated, ordered by id ascending. For synchronisation use /changes;
this endpoint is for browsing and targeted reads. Objects outside the caller's grant
are never returned (unknown ids in ids are silently omitted).
| ids | string Comma-separated product ids (max 100). Other filters are ignored when set. |
| brand_id | string (Id) ^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$ Examples: brand_id=prd_01J9ZK3Q7T5W Opaque, node-assigned identifier. Stable for the life of the object. Consumers MUST treat it as opaque. |
| updated_since | string <date-time> (DateTime) Examples: updated_since=2026-09-28T10:00:00Z Products whose own data, a variant or an asset changed at or after this time. |
| cursor | string (Cursor) [ 1 .. 4096 ] characters Opaque pagination or feed cursor. Clients MUST NOT parse or construct it. |
| limit | integer [ 1 .. 100 ] Default: 25 |
{- "items": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "style_number": "string",
- "name": { },
- "description": { },
- "item_type": "string",
- "gpc_brick": "string",
- "target_group": "string",
- "season": "string",
- "collection": "string",
- "materials": [
- {
- "part": "main",
- "fibers": [
- {
- "fiber": "string",
- "percentage": 1
}
]
}
], - "care": [
- {
- "code": "string",
- "text": { }
}
], - "country_of_origin": "NL",
- "attributes": {
- "property1": "string",
- "property2": "string"
}, - "created_at": "2026-09-28T10:00:00Z",
- "updated_at": "2026-09-28T10:00:00Z",
- "variants": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "product_id": "prd_01J9ZK3Q7T5W",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "gtin": "02000010010011",
- "gtin_original": "2000010010011",
- "sku": "string",
- "color": {
- "code": "string",
- "name": { },
- "supplier_color": "string"
}, - "size": "string",
- "size_system": "string",
- "updated_at": "2026-09-28T10:00:00Z"
}
], - "assets": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "product_id": "prd_01J9ZK3Q7T5W",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "media_type": "image",
- "color_code": "string",
- "variant_ids": [
- "prd_01J9ZK3Q7T5W"
], - "role": "packshot",
- "view": "front",
- "position": 1,
- "filename": "string",
- "alt_text": { },
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string",
- "rights": {
- "license": "retailer-use",
- "license_text": "string",
- "channels": [
- "webshop"
], - "territories": [
- "NL"
], - "embargo_until": "2026-09-28T10:00:00Z",
- "expires_at": "2026-09-28T10:00:00Z",
- "credit": "string",
- "model_release": true
}, - "provenance": {
- "ai_generated": true,
- "ai_edited": true,
- "source": "photo",
- "c2pa": "embedded",
- "generator": "string"
}, - "renditions": {
- "original": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "web": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "square": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "thumb": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}
}, - "updated_at": "2026-09-28T10:00:00Z"
}
]
}
], - "next_cursor": "string"
}| 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. |
{- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "style_number": "string",
- "name": { },
- "description": { },
- "item_type": "string",
- "gpc_brick": "string",
- "target_group": "string",
- "season": "string",
- "collection": "string",
- "materials": [
- {
- "part": "main",
- "fibers": [
- {
- "fiber": "string",
- "percentage": 1
}
]
}
], - "care": [
- {
- "code": "string",
- "text": { }
}
], - "country_of_origin": "NL",
- "attributes": {
- "property1": "string",
- "property2": "string"
}, - "created_at": "2026-09-28T10:00:00Z",
- "updated_at": "2026-09-28T10:00:00Z",
- "variants": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "product_id": "prd_01J9ZK3Q7T5W",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "gtin": "02000010010011",
- "gtin_original": "2000010010011",
- "sku": "string",
- "color": {
- "code": "string",
- "name": { },
- "supplier_color": "string"
}, - "size": "string",
- "size_system": "string",
- "updated_at": "2026-09-28T10:00:00Z"
}
], - "assets": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "product_id": "prd_01J9ZK3Q7T5W",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "media_type": "image",
- "color_code": "string",
- "variant_ids": [
- "prd_01J9ZK3Q7T5W"
], - "role": "packshot",
- "view": "front",
- "position": 1,
- "filename": "string",
- "alt_text": { },
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string",
- "rights": {
- "license": "retailer-use",
- "license_text": "string",
- "channels": [
- "webshop"
], - "territories": [
- "NL"
], - "embargo_until": "2026-09-28T10:00:00Z",
- "expires_at": "2026-09-28T10:00:00Z",
- "credit": "string",
- "model_release": true
}, - "provenance": {
- "ai_generated": true,
- "ai_edited": true,
- "source": "photo",
- "c2pa": "embedded",
- "generator": "string"
}, - "renditions": {
- "original": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "web": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "square": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "thumb": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}
}, - "updated_at": "2026-09-28T10:00:00Z"
}
]
}Same as the POST form, for up to 100 codes in the query string.
| gtin required | string Comma-separated codes (max 100). |
{- "results": [
- {
- "input": "string",
- "gtin": "02000010010011",
- "status": "matched",
- "product_id": "prd_01J9ZK3Q7T5W",
- "variant_id": "prd_01J9ZK3Q7T5W"
}
], - "products": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "style_number": "string",
- "name": { },
- "description": { },
- "item_type": "string",
- "gpc_brick": "string",
- "target_group": "string",
- "season": "string",
- "collection": "string",
- "materials": [
- {
- "part": "main",
- "fibers": [
- {
- "fiber": "string",
- "percentage": 1
}
]
}
], - "care": [
- {
- "code": "string",
- "text": { }
}
], - "country_of_origin": "NL",
- "attributes": {
- "property1": "string",
- "property2": "string"
}, - "created_at": "2026-09-28T10:00:00Z",
- "updated_at": "2026-09-28T10:00:00Z",
- "variants": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "product_id": "prd_01J9ZK3Q7T5W",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "gtin": "02000010010011",
- "gtin_original": "2000010010011",
- "sku": "string",
- "color": {
- "code": "string",
- "name": { },
- "supplier_color": "string"
}, - "size": "string",
- "size_system": "string",
- "updated_at": "2026-09-28T10:00:00Z"
}
], - "assets": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "product_id": "prd_01J9ZK3Q7T5W",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "media_type": "image",
- "color_code": "string",
- "variant_ids": [
- "prd_01J9ZK3Q7T5W"
], - "role": "packshot",
- "view": "front",
- "position": 1,
- "filename": "string",
- "alt_text": { },
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string",
- "rights": {
- "license": "retailer-use",
- "license_text": "string",
- "channels": [
- "webshop"
], - "territories": [
- "NL"
], - "embargo_until": "2026-09-28T10:00:00Z",
- "expires_at": "2026-09-28T10:00:00Z",
- "credit": "string",
- "model_release": true
}, - "provenance": {
- "ai_generated": true,
- "ai_edited": true,
- "source": "photo",
- "c2pa": "embedded",
- "generator": "string"
}, - "renditions": {
- "original": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "web": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "square": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "thumb": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}
}, - "updated_at": "2026-09-28T10:00:00Z"
}
]
}
]
}Each input is trimmed and spaces removed; 8, 12, 13 or 14 digits are normalised to
GTIN-14 by left-padding zeros and compared with Variant.gtin. Check digits are not
validated here (a wrong check digit simply won't match). Other inputs are invalid.
Duplicate inputs (after normalisation) are reported once. More than 2000 inputs:
400 limit_exceeded.
| gtins required | Array of strings <= 2000 items [ items <= 64 characters ] |
{- "gtins": [
- "string"
]
}{- "results": [
- {
- "input": "string",
- "gtin": "02000010010011",
- "status": "matched",
- "product_id": "prd_01J9ZK3Q7T5W",
- "variant_id": "prd_01J9ZK3Q7T5W"
}
], - "products": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "style_number": "string",
- "name": { },
- "description": { },
- "item_type": "string",
- "gpc_brick": "string",
- "target_group": "string",
- "season": "string",
- "collection": "string",
- "materials": [
- {
- "part": "main",
- "fibers": [
- {
- "fiber": "string",
- "percentage": 1
}
]
}
], - "care": [
- {
- "code": "string",
- "text": { }
}
], - "country_of_origin": "NL",
- "attributes": {
- "property1": "string",
- "property2": "string"
}, - "created_at": "2026-09-28T10:00:00Z",
- "updated_at": "2026-09-28T10:00:00Z",
- "variants": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "product_id": "prd_01J9ZK3Q7T5W",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "gtin": "02000010010011",
- "gtin_original": "2000010010011",
- "sku": "string",
- "color": {
- "code": "string",
- "name": { },
- "supplier_color": "string"
}, - "size": "string",
- "size_system": "string",
- "updated_at": "2026-09-28T10:00:00Z"
}
], - "assets": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "product_id": "prd_01J9ZK3Q7T5W",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "media_type": "image",
- "color_code": "string",
- "variant_ids": [
- "prd_01J9ZK3Q7T5W"
], - "role": "packshot",
- "view": "front",
- "position": 1,
- "filename": "string",
- "alt_text": { },
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string",
- "rights": {
- "license": "retailer-use",
- "license_text": "string",
- "channels": [
- "webshop"
], - "territories": [
- "NL"
], - "embargo_until": "2026-09-28T10:00:00Z",
- "expires_at": "2026-09-28T10:00:00Z",
- "credit": "string",
- "model_release": true
}, - "provenance": {
- "ai_generated": true,
- "ai_edited": true,
- "source": "photo",
- "c2pa": "embedded",
- "generator": "string"
}, - "renditions": {
- "original": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "web": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "square": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "thumb": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}
}, - "updated_at": "2026-09-28T10:00:00Z"
}
]
}
]
}Without cursor the node starts a snapshot: one upsert per currently visible
object (brands, then products, then variants, then assets), phase: snapshot; then the
feed switches to phase: live at the sequence number where the snapshot started.
With a cursor it returns entries after it. Pages never mix phases; the last snapshot
page has has_more: true.
Entries are at-least-once: the same object may appear several times, data is always
the CURRENT representation. Deletes are tombstones for anything no longer visible to
the caller. Tombstones are kept at least 90 days; an older cursor yields 410
cursor_expired (restart without cursor). A grant entry with resync: true means
restart without a cursor. next_cursor is always returned; poll again later
(RECOMMENDED: not more often than every 60 s when has_more is false, or on a
changes.available webhook).
| cursor | string (Cursor) [ 1 .. 4096 ] characters Opaque pagination or feed cursor. Clients MUST NOT parse or construct it. |
| limit | integer [ 1 .. 500 ] Default: 100 Page size, 1-500 (default 100). Nodes MAY return fewer. |
{- "changes": [
- {
- "seq": 1042,
- "type": "asset",
- "op": "delete",
- "id": "ast_7",
- "external_id": "KO-1001_NVY_2",
- "brand_id": "brd_kestrel",
- "product_id": "prd_1",
- "updated_at": "2026-09-28T10:00:00Z"
}, - {
- "seq": 1043,
- "type": "variant",
- "op": "upsert",
- "id": "var_9",
- "external_id": "2000100101025",
- "brand_id": "brd_kestrel",
- "product_id": "prd_1",
- "updated_at": "2026-09-28T10:00:00Z",
- "data": {
- "id": "var_9",
- "external_id": "2000100101025",
- "product_id": "prd_1",
- "brand_id": "brd_kestrel",
- "gtin": "02000100101025",
- "gtin_original": "2000100101025",
- "sku": "KO-1001-NVY-M",
- "color": {
- "code": "NVY",
- "name": {
- "en": "Navy",
- "nl": "Marineblauw"
}, - "supplier_color": "Deep Navy"
}, - "size": "M",
- "size_system": "INT",
- "updated_at": "2026-09-28T10:00:00Z"
}
}, - {
- "seq": 1044,
- "type": "grant",
- "op": "upsert",
- "id": "grt_3",
- "external_id": null,
- "brand_id": null,
- "product_id": null,
- "updated_at": "2026-09-28T10:00:00Z",
- "resync": true,
- "data": {
- "id": "grt_3",
- "retailer": {
- "id": "rtl_3",
- "name": "Example Store"
}, - "brand_ids": null,
- "scopes": [
- "catalog:read",
- "media:read"
], - "original_allowed": false,
- "status": "active",
- "updated_at": "2026-09-28T10:00:00Z"
}
}
], - "next_cursor": "bGl2ZToxMDQ0",
- "has_more": false,
- "phase": "live"
}Use this to refresh expired signed rendition URLs.
| 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. |
{- "id": "prd_01J9ZK3Q7T5W",
- "external_id": "KO-1001",
- "product_id": "prd_01J9ZK3Q7T5W",
- "brand_id": "prd_01J9ZK3Q7T5W",
- "media_type": "image",
- "color_code": "string",
- "variant_ids": [
- "prd_01J9ZK3Q7T5W"
], - "role": "packshot",
- "view": "front",
- "position": 1,
- "filename": "string",
- "alt_text": { },
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string",
- "rights": {
- "license": "retailer-use",
- "license_text": "string",
- "channels": [
- "webshop"
], - "territories": [
- "NL"
], - "embargo_until": "2026-09-28T10:00:00Z",
- "expires_at": "2026-09-28T10:00:00Z",
- "credit": "string",
- "model_release": true
}, - "provenance": {
- "ai_generated": true,
- "ai_edited": true,
- "source": "photo",
- "c2pa": "embedded",
- "generator": "string"
}, - "renditions": {
- "original": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "web": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "square": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}, - "thumb": {
- "name": "original",
- "url_expires_at": "2026-09-28T10:00:00Z",
- "content_type": "image/jpeg",
- "width": 1,
- "height": 1,
- "bytes": 1,
- "sha256": "string"
}
}, - "updated_at": "2026-09-28T10:00:00Z"
}Authorised by Bearer token (scope media:read; media:original for original) OR by a
valid signature in the URL as handed out in Rendition.url. Supports Range
(206), If-None-Match (304). ETag is the quoted sha256; OBX-Content-SHA256 is the
sha256 of the full representation. original bytes are served exactly as uploaded
(embedded C2PA is preserved). Nodes log each download for the brand's statistics.
| 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. |
| name required | string (RenditionName) Enum: "original" "web" "square" "thumb" Rendition name. |
| Range | string |
| If-None-Match | string |
{- "title": "string",
- "status": 400,
- "detail": "string",
- "instance": "string",
- "obx_error": "invalid_request",
- "errors": [
- {
- "pointer": "string",
- "obx_error": "invalid_request",
- "detail": "string"
}
], - "retry_after": 0
}Capability exports. Asynchronous; poll GET /exports/{id} (RECOMMENDED every 10 s) or
subscribe to export.completed. Nodes MAY limit concurrent exports per client (409
conflict with the running export id in detail).
| Idempotency-Key | string <= 200 characters Repeating a request with the same key within 24 h returns the original response. |
object All given filters must match (AND). Empty = everything visible. | |
| renditions required | Array of strings (RenditionName) unique Items Enum: "original" "web" "square" "thumb" Renditions to include as files; [] = data only. 'original' requires media:original. |
| include_data | boolean Default: true Include product/variant/asset lines in the manifest (default true). |
| ^x-[a-z0-9]+-[a-z0-9_-]+$ pattern property | any |
{- "filter": {
- "brand_ids": [
- "brd_kestrel"
], - "product_ids": null,
- "gtins": null,
- "updated_since": null
}, - "renditions": [
- "web"
], - "include_data": true
}{- "id": "prd_01J9ZK3Q7T5W",
- "status": "queued",
- "created_at": "2026-09-28T10:00:00Z",
- "finished_at": "2026-09-28T10:00:00Z",
- "expires_at": "2026-09-28T10:00:00Z",
- "progress": {
- "done": 0,
- "total": 0
}, - "error": {
- "title": "string",
- "status": 400,
- "detail": "string",
- "instance": "string",
- "obx_error": "invalid_request",
- "errors": [
- {
- "pointer": "string",
- "obx_error": "invalid_request",
- "detail": "string"
}
], - "retry_after": 0
}
}| 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. |
{- "id": "exp_1",
- "status": "succeeded",
- "created_at": "2026-09-28T10:00:00Z",
- "finished_at": "2026-09-28T10:00:00Z",
- "expires_at": "2026-09-29T10:00:00Z",
- "progress": {
- "done": 27,
- "total": 27
}, - "parts": [
- {
- "number": 1,
- "bytes": 1048576,
- "sha256": "0000000000000000000000000000000000000000000000000000000000000000",
- "files": 27
}
], - "error": null
}| 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. |
{- "type": "file",
- "asset_id": "prd_01J9ZK3Q7T5W",
- "rendition": "original",
- "part": 1,
- "path": "string",
- "bytes": 1,
- "sha256": "string"
}| 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 |
| Range | string |
{- "title": "string",
- "status": 400,
- "detail": "string",
- "instance": "string",
- "obx_error": "invalid_request",
- "errors": [
- {
- "pointer": "string",
- "obx_error": "invalid_request",
- "detail": "string"
}
], - "retry_after": 0
}Sent by the node to each active subscription. Headers: OBX-Webhook-Id (event id),
OBX-Webhook-Timestamp (unix seconds), OBX-Webhook-Signature: v1=<hex> where
hex = HMAC-SHA256(secret, <timestamp>.<raw body>). Receivers MUST verify in constant
time and reject timestamps more than 300 s away. Respond 2xx within 10 s; otherwise the
node retries with exponential backoff for up to 24 h. Webhooks are wake-up calls:
always fetch state via /changes.
| OBX-Webhook-Id required | string |
| OBX-Webhook-Timestamp required | string^[0-9]+$ |
| OBX-Webhook-Signature required | string^v1=[a-f0-9]{64}(, ?v1=[a-f0-9]{64})*$ |
| id required | string [ 1 .. 128 ] characters Unique event id (use for de-duplication). |
| type required | string^[a-z_]+\.[a-z_]+$ Protocol: changes.available, grant.updated, export.completed. Brand admin: access_request.created, import_run.completed, ingest_job.completed. Both: webhook.test. Receivers MUST ignore unknown types. |
| created_at required | string <date-time> (DateTime) RFC 3339 timestamp, UTC recommended. |
| data required | object changes.available: {latest_seq}. grant.updated: {grant: Grant}. export.completed: {export_id, status}. access_request.created: {access_request_id, retailer_name}. import_run.completed: {import_run_id, status, counts}. ingest_job.completed: {job_id, status}. webhook.test: {}. Webhooks are wake-up calls: always fetch state through the API. |
| ^x-[a-z0-9]+-[a-z0-9_-]+$ pattern property | any |
{- "id": "string",
- "type": "string",
- "created_at": "2026-09-28T10:00:00Z",
- "data": { }
}{- "items": [
- {
- "id": "prd_01J9ZK3Q7T5W",
- "events": [
- "string"
], - "secret": "string",
- "status": "active",
- "created_at": "2026-09-28T10:00:00Z",
- "last_delivery_at": "2026-09-28T10:00:00Z",
- "last_status": 0
}
]
}Capability webhooks. Events for consumers: changes.available, grant.updated,
export.completed. The URL MUST be https and MUST NOT resolve to a private, loopback or
link-local address (422 unsafe_url). Max 5 subscriptions per client.
| url required | string <uri> (Url) <= 2048 characters ^https?:// Absolute http(s) URL. |
| events required | Array of strings non-empty Items Enum: "changes.available" "grant.updated" "export.completed" |
{- "events": [
- "changes.available"
]
}{- "id": "whk_1",
- "events": [
- "changes.available",
- "grant.updated"
], - "secret": "whsec_Tm90LWEtcmVhbC1zZWNyZXQtZXhhbXBsZQ",
- "status": "active",
- "created_at": "2026-09-28T10:00:00Z",
- "last_delivery_at": null,
- "last_status": null
}| 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. |
{- "title": "string",
- "status": 400,
- "detail": "string",
- "instance": "string",
- "obx_error": "invalid_request",
- "errors": [
- {
- "pointer": "string",
- "obx_error": "invalid_request",
- "detail": "string"
}
], - "retry_after": 0
}| 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. |
{- "delivered": true,
- "status": 0
}Capability usage. Organisation-level events only; never personal data. Unknown asset ids are ignored.
required | Array of objects (UsageEvent) [ 1 .. 1000 ] items |
| ^x-[a-z0-9]+-[a-z0-9_-]+$ pattern property | any |
{- "events": [
- {
- "asset_id": "ast_1",
- "rendition": "web",
- "event": "published",
- "channel": "webshop",
- "at": "2026-09-28T10:00:00Z",
- "count": 1
}, - {
- "asset_id": "ast_2",
- "rendition": null,
- "event": "synced",
- "channel": null,
- "at": "2026-09-28T10:00:00Z"
}
]
}{- "accepted": 0
}