Order Lifecycle
Understand the complete order flow from validation through fulfillment.
Order Flow
Every order goes through two API steps: validation and placement.
Reading the diagram
"Errors?" means isValid: false on the HTTP 200 validateOrder response — business/order problems never produce a 4xx from validate.
End-user client context
Required on every order request
Both order calls require a client object carrying the end user's originating IP and user agent. Requests without it are rejected with a 400 ProblemDetail (errorCode: CLIENT_CONTEXT_REQUIRED).
This is a breaking change for existing integrations. The enforcement date is being communicated to each live partner directly; if you have not received one, contact your Gett integration lead before shipping.
Code
Your order requests reach Gett server-to-server, so the connection we see belongs to your backend rather than to the person ordering. The commerce partners we forward orders to run anti-fraud checks that need the real customer's signals, so you have to forward them from the request your own front end received — the same request that produced the cart.
We reject rather than substitute a placeholder: a synthetic address forwarded to a partner counts against the integration in the traffic reviews they run, and it would do so silently.
Three named errors cover the field:
errorCode | Cause |
|---|---|
CLIENT_CONTEXT_REQUIRED | client absent, or ip/userAgent missing or blank |
CLIENT_IP_INVALID | ip unparseable, or in a range no end user can originate from — private, loopback, link-local, CGNAT, reserved |
CLIENT_USER_AGENT_INVALID | userAgent longer than 512 characters |
If your front end sits behind a proxy or CDN, client.ip is the first address in the forwarded-for chain, not your own server's. client is declared on OrderCore, so it applies to validate and place alike — see Schema inheritance.
Validation vs Placement
The API uses a two-step model: a soft pre-check (validateOrder) followed by the committing transaction (placeOrder). Understanding when each returns errors — and in what shape — is central to a correct integration.
How each call behaves
POST /v1/marketfront/orders/validate is a dry run. It computes real server-side pricing and checks availability without writing anything. Think of it like Square's CalculateOrder or Stripe's requirements.errors — a preview you can show to the user before they commit.
- Always returns HTTP 200 (or 401 for auth failures). There is no 4xx for business/order problems.
- On 200 the body is the full
Orderobject, including a computedamountsbreakdown and anerrors[]array. - The read-only boolean
isValidon the order tells you whether placement is safe to proceed. WhenisValid: false,errors[]is non-empty and describes what is wrong. WhenisValid: trueanderrorsis empty, the order passed all checks at that moment. - Because validate is read-only it is naturally idempotent — no
Idempotency-Keyheader is required.
POST /v1/marketfront/orders/place is the committing call. It re-validates, charges the customer, and submits the order to the restaurant.
- On success it returns 2xx with the authoritative post-placement
Order. - On failure it returns 4xx as an RFC 9457
application/problem+jsonProblemDetail. The400response covers payment declines, store rejections, and missing idempotency keys; the top-levelerrorCodefield (e.g.PAYMENT_DECLINED,MISSING_IDEMPOTENCY_KEY) names the overall failure reason.
Decision table
| Situation | Endpoint | HTTP | Key fields | Partner action |
|---|---|---|---|---|
| Validate clean | validateOrder | 200 | isValid: true, errors: [], amounts populated | Show pricing preview; proceed to place |
| Validate with problems | validateOrder | 200 | isValid: false, errors[] non-empty | Surface errors to user; do not call place |
Unknown errors[].code value | validateOrder | 200 | unrecognized code in errors[] | Treat as OTHER; show a generic message |
| Place success | placeOrder | 2xx | Order with final amounts | Confirm order to user; start tracking |
| Place hard failure | placeOrder | 4xx | ProblemDetail + top-level errorCode + errors[] | Handle per errorCode; retry with same idempotency key only for PAYMENT_FAILED |
| Malformed request | Either | 400 / 422 | ProblemDetail | Fix request body before retrying |
A clean validate does not guarantee place succeeds
Stock levels, prices, and payment authorization can change in the window between the two calls. Always inspect errors even on a 200 validate response, and always be prepared for placeOrder to return a 4xx even after a successful validate.
Unified OrderError shape
OrderError is the same object on both paths — order.errors[] on a validate 200 and ProblemDetail.errors[] on a place 4xx. You only need one error-rendering path in your client:
| Field | Type | Description |
|---|---|---|
code | string (open enum) | Machine-readable reason — see Error codes below |
message | string | Curated, user-facing description |
pointer | string | RFC 6901 JSON Pointer to the offending element, e.g. /cart/lineItems/0 |
Note the distinction between OrderError.code (per-element, in errors[]) and the top-level ProblemDetail.errorCode (single string naming the overall place failure). Both can appear on a 4xx place response but they serve different purposes.
Error codes
errors[].code is an open enum (x-extensible-enum). The list below is the current set, but it is not exhaustive — Gett may add new codes at any time as new failure modes are identified. Following the Zalando API extension convention: clients must provide default/fallback behaviour for unknown values (route to OTHER) and must not hard-fail or throw on an unrecognized code. Do not deserialize into a closed/strict enum type.
| Code | Meaning | Suggested action |
|---|---|---|
ITEM_UNAVAILABLE | An item in the cart is no longer available | Remove the item and re-validate |
PAYMENT_FAILED | Transient payment processing failure | Retry place with the same idempotency key |
STORE_CLOSED | Store is not accepting orders right now | Re-discover stores or try a scheduled order |
DELIVERY_UNAVAILABLE | Delivery is not available for this order | Offer pickup or a different store |
OUTSIDE_AVAILABILITY_WINDOW | Requested scheduled time is outside the store's hours | Adjust scheduledTime or switch to ASAP |
MINIMUM_NOT_MET | Order total is below the store's minimum | Add more items |
ORDER_TOTAL_DIFFERENT | Total changed since the order was last validated | Re-validate to get the updated amounts |
OTHER | Catch-all for any reason not covered above, and the fallback for unrecognized codes | Show a generic error message |
Order State & Status
Orders use two fields: state (lifecycle position) and status (granular progress).
State
Status
Within the open state, status tracks progress:
For complete details on state values, status values, and error codes, see the API Reference.
Fulfillment
Every order carries a fulfillment object that is a discriminated union on the mode property. The two variants are PICKUP and DELIVERY_BY_MERCHANT.
Common fields (all variants)
| Field | Required | Description |
|---|---|---|
mode | Yes | Discriminator: PICKUP or DELIVERY_BY_MERCHANT |
scheduleType | Yes | ASAP or SCHEDULED |
scheduledTime | Conditional | UTC ISO-8601 date-time. Required when scheduleType is SCHEDULED; must be null (or omitted) when ASAP. |
Delivery-only fields (DELIVERY_BY_MERCHANT)
| Field | Required | Description |
|---|---|---|
address | Yes | Delivery destination. On a request, provide either an addressId reference or inline street fields. On a response, the fully resolved and geocoded address is returned. |
deliveryType | Yes | DOOR_TO_DOOR or LEAVE_AT_DOOR |
instructions | No | Free-text delivery instructions, max 500 characters |
Examples
PICKUP — ASAP
Code
DELIVERY_BY_MERCHANT — scheduled
Code
Fulfillment vs store discovery
The fulfillment object on an order (discriminated union above) is distinct from the fulfillmentType flat enum (PICKUP | DELIVERY_BY_MERCHANT) used on DiscoverStoresRequest to filter search results. They share the same values but serve different purposes — one describes how to fulfill a specific order, the other filters which stores to surface.
Idempotency
POST /v1/marketfront/orders/place requires an Idempotency-Key header. Calls without it are rejected with a 400 ProblemDetail (errorCode: MISSING_IDEMPOTENCY_KEY).
- Generate a fresh UUID v4 per distinct order and include it on every retry of that order.
- A replay with the same key returns the original response (24h TTL) — safe to retry on network errors and 5xx without risking a duplicate charge.
- Do not reuse a key with a different payload. Today the cached response is returned regardless of body; a future release will reject same-key/different-body with
422. Keep keys 1:1 with payloads now to avoid surprises.
validate is naturally idempotent (read-only pricing) and does not use the header.
Related
- Validate Order — Check availability and pricing
- Place Order — Submit order for fulfillment
- Payments — Payment options and Card-on-File setup
- Webhooks — Signature verification and retry policy for order status webhooks