Response format
Successes and failures share one JSON envelope. The HTTP status is the real outcome — an error is never hidden inside a 200 — and a failure carries a stable, symbolic code to branch on.
{ "status": "OK", "message": "Credential is valid", "meta": { "version": "1.0", "timestamp": "2026-10-19T10:10:55Z", "request_id": "6b183f2425c81699f1f3d3e5" }, "data": { "pong": true, "client": { "environment": "live", "key_prefix": "cru_live_7KQ2...", "organisation": "Example Wine Merchants Ltd", "label": "Partner integration", "market": "uk", "currency": "GBP", "scopes": [ "meta:read", "market:read", "bids:read", "bids:write" ], "expires_at": null } } }
Envelope fields
statusstring
OK on success, ERROR on failure. It describes the call, never a resource.
messagestring
A sentence for people. It is not stable: never branch on it.
metaobject
version ("1.0"), timestamp (RFC 3339 UTC) and request_id, which is also sent as X-Request-Id.
meta.paginationobject · paged endpoints
limit, offset, total and has_more. What is counted depends on the endpoint, and /wines has no total.
dataobject | null
The payload. Null on error.
errorsarray · failures only
Absent on success. On failure, one entry per problem: code, message, and field and details where they apply.
Errors
On failure status is ERROR, data is null, and errors holds one entry per problem. There is usually exactly one; an unknown query parameter is reported for every one you sent, each with its field.
{ "status": "ERROR", "message": "This client does not have permission to perform this operation", "meta": { "version": "1.0", "timestamp": "2026-10-19T10:10:55Z", "request_id": "6b183f2425c81699f1f3d3e5" }, "data": null, "errors": [ { "code": "AUTH_INSUFFICIENT_SCOPE", "message": "This credential is not granted the scope this endpoint requires", "details": { "required_scope": "market:read" } } ] }
Each item in errors[]
codestring
A stable, symbolic code such as AUTH_INSUFFICIENT_SCOPE. Branch on this.
fieldstring · optional
The query parameter or body field the problem is about.
messagestring
What went wrong. For TRADE_REFUSED and PRICE_CHANGED it is the reason, worded so it can be shown to a customer.
detailsobject · optional
Structured context: required_scope, store and allowed_stores, allowed_methods, retry_after.
Every error code, and what to do about each →
Pagination
Endpoints that page add meta.pagination. has_more is true while offset + limit is less than total; advance offset by limit until it is false. What a page counts differs by endpoint — see the table.
{ "status": "OK", "message": "Bids retrieved successfully", "meta": { "version": "1.0", "timestamp": "2026-10-19T10:10:55Z", "request_id": "6b183f2425c81699f1f3d3e5", "pagination": { "limit": 50, "offset": 0, "total": 1, "has_more": false } }, "data": { "bids": [ { "code": "BID-la4FCBHRAnKZ2vOaR8VF1KnHDYmFdaa_5N_mrFEFWzQ", "client_ref": "PO-2026-0193", "lwin18": "101187220240600750", "wine_name": "Lafite Rothschild 2024 (6x75cl)", "vintage": 2024, "pack_size": "6x75cl", "product_url": "https://markets.cruworldwine.com/uk/product/lafite-rothschild-2024-6x75cl", "price": 1650.5, "currency": "GBP", "bid_currency": "GBP", "quantity": 1, "quantity_matched": 0, "quantity_open": 1, "order_numbers": [], "order_urls": [], "status": "live", "renewable": false, "market": "uk", "local_only": false, "auto_transfer": false, "expires_on": "2027-01-17", "created_at": "2026-10-19T10:10:54Z", "updated_at": "2026-10-19T10:10:54Z" } ] } }
Status codes
200 | A read, or a write that changed or cancelled something. |
201 | A write that created something: a bid, a listing, a purchase, an inbond address or a suggested warehouse. |
202 | A connected app’s write, staged and not yet done. Confirm it → |
204 | Discarding a staged instruction. No body and no envelope — only the X-Request-Id header. |
4xx, 5xx | The error envelope, with errors[].code. |
Response headers
X-Request-Id | On every response. Yours, if you sent a valid one; otherwise Cru’s. |
Idempotent-Replayed: true | The write, or confirmation, already ran: this is its stored outcome, replayed rather than done again. |
Allow | On 405: the methods the URL accepts. |
WWW-Authenticate | Where connected apps are enabled: on 401, and on 403 for a missing scope when a bearer token was sent — where a connected app can get a token, and which scope it needs. |
Cache-Control: no-store | On every JSON response: it is never cached, and the API sets no cookies. A photo is the exception, cached for a day with an ETag. |
meta.request_id is also returned as X-Request-Id. Include it when contacting Cru about a request.
Earlier drafts of this reference described http_code, internal_error_code, api_info and page_info. They are gone: use the HTTP status, errors[].code, meta and meta.pagination instead.