Response envelope
Every endpoint — success or failure — returns the same JSON shape:
{
"status": "SUCCESS",
"statusCode": 0,
"statusMessage": "Human-readable message",
"traceId": "uuid-hex-string",
"data": { },
"errors": null
}
| Field | Type | Meaning |
|---|---|---|
status | TransactionStatus | Outcome of this request/operation — not always the outcome of an order |
statusCode | int (0–14) | Internal request-handling code: replay, accepted, validation error. Not an HTTP status, not an order status |
statusMessage | string | Human-readable message |
traceId | string | Correlation id, mirrors the X-Trace-Id header |
data | object | null | Payload, null on error |
errors | array | null | Field-level validation errors |
TransactionStatus
| Value | Meaning |
|---|---|
SUCCESS | Terminal success |
IN_PROGRESS | Working — poll for the result |
PARTIALLY_COMPLETED | Terminal partial success (some items delivered, the rest refunded). A single order or a cart aggregate can have this value |
CANCELLED | Failure or refusal. All errors return CANCELLED |
Three status surfaces
There are three places a status can appear in one response, and they answer different questions. Most integration bugs are a confusion between them.
| Where | Field | Answers | Trust it for |
|---|---|---|---|
| Envelope | status | How did this request end | Depends on the endpoint (below) |
| Envelope | statusCode | How to handle the request: retry, replay, validation error | Retry / idempotency logic — not the order outcome |
| Payload | data.status, data.orders[].status, data.page.items[].status | The order lifecycle: delivered / working / cancelled | ✅ The only source of truth about an order |
What the envelope status means depends on which endpoint you called:
| Endpoint | Envelope status |
|---|---|
POST /orders | Mirrors data.status. IN_PROGRESS → HTTP 202 + Retry-After |
POST /carts | Aggregate over the cart. Individual order statuses are in data.orders[].status |
GET /orders, catalog, balances, any successful list/read | SUCCESS = "request served". It says nothing about the orders inside — for GET /orders, read data.page.items[].status |
The integration rule, in one line
Branch business logic (delivered / wait / failed) only on
data[...].status. The envelopestatusandstatusCodedescribe the request, not the fate of the order.
Two non-obvious traps (both by design)
- HTTP 200 ≠ the order succeeded. A cancelled order comes back with HTTP
200 and
statusCode0or2; the failure is visible only asdata.status = CANCELLED. The request was served fine — the order failed. Never mark an order delivered based on the HTTP code or the envelope status. status: SUCCESSon a list means nothing about the orders in it. OnGET /ordersand every other list/read endpoint the envelope status is alwaysSUCCESS. The real statuses live in each element ofdata.
Field-level errors
Validation failures fill errors with one entry per offending field:
{
"status": "CANCELLED",
"statusCode": 3,
"statusMessage": "Validation error",
"traceId": "...",
"data": null,
"errors": [
{"field": "orders.0.denominationId", "code": "INVALID_VALUE", "message": "Item not found"}
]
}
code | Meaning |
|---|---|
MISSING | Required field absent |
OUT_OF_RANGE | Value outside the allowed range |
INVALID_FORMAT | Malformed value |
INVALID_VALUE | Well-formed but not acceptable |
NOT_ALLOWED | Value not permitted here |
TOO_LONG | Too long |
TOO_SHORT | Too short |
Tracing
Send X-Trace-Id and we echo it back in the envelope and in every log line for
that request; omit it and we generate one. The response header X-Trace-Id is
CORS-exposed, so a browser-side debug tool can read it. Quote this id in
support tickets — it is how we find your request.