API Conventions
This section covers the core mechanics of the Marketfront API, including error handling, rate limiting, and caching.
Authentication
All requests require a Bearer token in the Authorization header. See the Authentication guide for key management, environment setup, and security best practices.
Money & amounts
Every monetary value is a self-describing money object: the amounts breakdown carries a currency (ISO 4217, e.g. USD) and each amount is a decimal string — "10.50", never a bare JSON number.
Code
Send amounts the same way you receive them — as strings (e.g. amounts.tip on validateOrder).
Why strings? Parsing money as a JSON number invites IEEE-754 drift (0.1 + 0.2 !== 0.3). A decimal string parsed with a decimal/big-decimal type on your side is exact. Currency is explicit so you never have to assume USD.
Schema inheritance
Some schemas in the API reference render as allOf: [{$ref: Base}, {additional properties}]. Read this as inheritance — the object has all the fields from the referenced base schema, plus the additional ones listed. For example, DeliveryFulfillment extends Fulfillment (adding address, deliveryType, instructions), and OrderPlaceRequest extends OrderValidateRequest (adding payment).
This is why a field can appear once and apply everywhere. The required client object is declared on OrderCore, so both validateOrder and placeOrder carry it — see End-user client context.
Decimal places per currency
An amount must not carry more decimal places than its currency's minor unit, or the request is rejected with 422 PRECISION_EXCEEDED. Round to the currency's scale before sending.
| Minor-unit scale | Example currencies | Example |
|---|---|---|
| 2 (default) | USD, EUR, GBP, CAD, AUD | "10.50" |
| 0 | JPY, KRW, VND, CLP | "1000" |
| 3 | BHD, KWD, OMR, TND | "10.500" |
Error Handling
All error responses use RFC 9457 application/problem+json format. Every error includes machine-readable fields (errorCategory, retryable, retryAfter) for programmatic handling. Domain-specific errors include an errorCode field identifying the specific issue.
The API supports content negotiation via the Accept header — request text/markdown for a compact text representation suitable for AI agents and CLI tooling. See the Error Reference for full details.
HTTP Status Codes
| Status | Meaning |
|---|---|
400 | Invalid request body, missing fields, or domain-specific errors (see errorCode) |
401 | Missing or invalid API key |
404 | Resource does not exist |
409 | Cart or store conflict — CART_STALE, STORE_PAUSED, ENVIRONMENT_MISMATCH |
422 | Request-shape or semantic validation of the request body failed (e.g. malformed body, PRECISION_EXCEEDED) |
429 | Rate limit exceeded |
500 | Unexpected server error |
Order/business validation is different
validateOrder never returns a 4xx for business or order problems (item unavailable, store closed, minimum not met, etc.). It returns HTTP 200 with isValid: false and a populated errors[] on the Order body. Request-shape, authentication, and cart/store conflict errors still produce 4xx from that endpoint — a stale cart, a paused store, an environment mismatch (409) or an over-precise amount (422) are raised before the order is ever built, so handle both shapes. See Validation vs Placement in the Order Lifecycle guide.
Error Codes
Domain-specific errors include an errorCode field.
Two kinds of code appear below
The tables in this section list both:
- Top-level
ProblemDetail.errorCode— names an overall 4xx failure and carries an HTTP status, anerrorCategoryand aretryableflag. errors[].code— a per-element reason on an order. These arrive on an HTTP200response withisValid: falseand have no status, category or retryable of their own.
The Kind column on each table below says which one a code is. The authoritative list of errors[].code reasons is the Order Lifecycle guide.
Cart & Item Errors
| Code | Kind | Description | Action |
|---|---|---|---|
CATALOGSET_REQUIRED | errorCode 400 | Cart creation requires a catalogSetId | Include catalogSetId when cart is null |
ITEM_UNAVAILABLE | errors[].code | Item no longer available | Remove from cart and re-validate |
MINIMUM_NOT_MET | errors[].code | The whole order is below the store's order minimum | Add more items |
MODIFIER_REQUIRED | errors[].code | One item is missing a required choice | Resolve pointer to that line item and have the user pick the option |
Store Errors
| Code | Kind | Description | Action |
|---|---|---|---|
STORE_CLOSED | errors[].code | Store is not accepting orders right now | Re-discover stores |
OUTSIDE_AVAILABILITY_WINDOW | errors[].code | Requested scheduled time is outside the store's hours | Adjust scheduledTime or switch to ASAP |
DELIVERY_UNAVAILABLE | errors[].code | Delivery is not available for this order | Offer pickup or a different store |
STORE_PAUSED | errorCode 409 | Gett has paused this store | Offer another store, or retry after the pause lapses |
STORE_NOT_FOUND | errorCode 404 | Store does not exist | Re-discover stores |
CATALOGSET_NOT_FOUND | errorCode 404 | CatalogSet does not exist | Re-discover for updated catalogSetId |
Payment Errors
| Code | Kind | Description | Action |
|---|---|---|---|
PAYMENT_FAILED | errors[].code | Payment declined without an attributable reason | Ask the user for a different payment method |
PAYMENT_TOKEN_INVALID | errorCode 400 | paymentToken is not usable for this order | Re-vault the card and quote the new token |
CARD_EXPIRED | both | Card has expired, or expired while stored | Collect and vault a new card |
INSUFFICIENT_FUNDS | errors[].code | Declined — the account does not cover the amount | Ask the user for a different payment method |
INCORRECT_CVC | errors[].code | Declined — the security code did not match | Let the user re-enter the security code and retry the same card |
CARD_LOST_OR_STOLEN | errors[].code | The card cannot be used | Ask for a different method. Show a generic message — never tell a cardholder their card is flagged |
PROCESSING_ERROR | errors[].code | A temporary fault at the processor, not a decision about the card | Retry the same card with a fresh idempotency key — see the page for the double-charge caveat |
PAYMENT_DECLINED | errorCode (reserved) | Declined by issuer — not currently emitted | Use a different payment method |
Order Errors
| Code | Kind | Description | Action |
|---|---|---|---|
ORDER_TOTAL_DIFFERENT | errors[].code | Total changed since validation | Re-validate the order |
OTHER | errors[].code | Catch-all, and the fallback for any unrecognized code | Show a generic error message |
CART_STALE | errorCode 409 | The store's menu changed since the cart was built | Start a new cart from the current menu |
ENVIRONMENT_MISMATCH | errorCode 409 | Store is not available for your account's environment | Check sandbox vs live credentials |
IDEMPOTENCY_KEY_CONFLICT | errorCode 422 | Idempotency-Key reused with a different request body, or by a different partner or end user | Use a fresh key for a new order, or replay the same body as the same partner and end user |
IDEMPOTENCY_KEY_IN_PROGRESS | errorCode 409 | An earlier request on this Idempotency-Key is still in flight | Retry unchanged on the same key — a fresh key here places a duplicate order |
Open enum: order errors[].code
The errors[].code values on an order are an open enum (x-extensible-enum). The reason codes listed above are current but not exhaustive — new codes may be added as new failure modes are identified. Clients must handle unknown values as OTHER and must not hard-fail on an unrecognized code. Do not deserialize into a closed/strict enum type. See the full code table with suggested actions in the Order Lifecycle guide.
This is distinct from the top-level ProblemDetail.errorCode field (a single string naming the overall failure reason on a 4xx place response).
Authentication Errors
| Code | HTTP | Description | Action |
|---|---|---|---|
SESSION_USER_REQUIRED | 401 | Endpoint requires an authenticated user identity | Ensure the end user is signed in before making this request |
Retry Strategy
| Category | Retryable? | Strategy |
|---|---|---|
401 Unauthorized | No | Fix your API key |
400 Bad Request | No | Fix the request |
404 Not Found | No | Resource doesn't exist |
429 Too Many Requests | Yes | Wait the number of seconds in Retry-After, then retry |
500 Internal Error | Yes | Exponential backoff (1s, 2s, 4s, max 30s) |
Always include the
requestIdwhen contacting support about a specific error.
Rate Limits
One limit: 1,000 requests per minute, per partner organization, shared across every endpoint.
There are no per-endpoint tiers. Reads, writes and store discovery all draw on the same counter, so
1,000 calls to /stores/discover and 1,000 calls to /orders/place in the same minute are 2,000
requests against one budget, not two budgets of 1,000.
Rate limits are scoped to your partner organization — all keys belonging to the same organization share the same quota. Sandbox and production draw on the same limit; there is no sandbox multiplier.
When you are limited
The API returns 429 Too Many Requests with a Retry-After header giving the number of seconds
to wait. That is the only rate-limit header we send — there are no X-RateLimit-* headers, so do not
build against a running remaining-count.
Wait for Retry-After seconds, then retry. If the header is absent, use exponential backoff starting
at 1 second.
A 429 is shaped differently from our other errors
Rate limiting is enforced at the gateway, ahead of the API. A 429 is therefore generated by the
gateway itself and does not carry the ProblemDetail envelope the rest of
this page describes — no errorCode, no errorCategory, no retryAfter body field. Branch on the
429 status and the Retry-After header, not on the body.
Caching Strategy
| Data Type | Strategy | Why |
|---|---|---|
| Store results | Never cache | Store status changes constantly |
| CatalogSets | Cache aggressively | Immutable — use catalogSetId as cache key |
| Validation tokens | Expire in 15 min | Security and price accuracy |