# 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](/distribution-partners/shared-guides/authentication) 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.

```json
{
  "amounts": {
    "currency": "USD",
    "subTotal": "18.00",
    "fees": "2.99",
    "taxes": "1.71",
    "tip": "3.00",
    "total": "25.70"
  }
}
```

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](/distribution-partners/marketfront-api/guides/order-lifecycle).

### 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`](/errors/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](https://www.rfc-editor.org/rfc/rfc9457) `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](/errors) 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` | Conflict (e.g., order token already used) |
| `422` | Request-shape or semantic validation of the request body failed (e.g. malformed body, [`PRECISION_EXCEEDED`](/errors/PRECISION_EXCEEDED)) |
| `429` | Rate limit exceeded |
| `500` | Unexpected server error |

:::note[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. Only malformed requests and authentication failures produce 4xx from that endpoint. See [Validation vs Placement](/distribution-partners/marketfront-api/guides/order-lifecycle#validation-vs-placement) in the Order Lifecycle guide.
:::

### Error Codes

Domain-specific errors include an `errorCode` field.

#### Cart & Item Errors

| Code | Description | Action |
|------|-------------|--------|
| [`CART_EMPTY`](/errors/CART_EMPTY) | Cart has no items | Add items before validating |
| [`CATALOGSET_REQUIRED`](/errors/CATALOGSET_REQUIRED) | Cart creation requires a catalogSetId | Include `catalogSetId` when cart is null |
| [`ITEM_UNAVAILABLE`](/errors/ITEM_UNAVAILABLE) | Item no longer available | Remove from cart and re-validate |
| [`MODIFIER_REQUIRED`](/errors/MODIFIER_REQUIRED) | Required modifier group has no selection | Prompt user to select |
| [`ORDER_BELOW_MINIMUM`](/errors/ORDER_BELOW_MINIMUM) | Below store's order minimum | Add more items |

#### Store Errors

| Code | Description | Action |
|------|-------------|--------|
| [`STORE_CLOSED`](/errors/STORE_CLOSED) | Store is no longer open | Re-discover stores |
| [`STORE_NOT_FOUND`](/errors/STORE_NOT_FOUND) | Store does not exist | Re-discover stores |
| [`CATALOGSET_NOT_FOUND`](/errors/CATALOGSET_NOT_FOUND) | CatalogSet does not exist | Re-discover for updated `catalogSetId` |

#### Payment Errors

| Code | Description | Action |
|------|-------------|--------|
| [`PAYMENT_FAILED`](/errors/PAYMENT_FAILED) | Transient processing failure | Retry with the same idempotency key |
| [`PAYMENT_METHOD_INVALID`](/errors/PAYMENT_METHOD_INVALID) | Payment method payload not valid | Re-prompt the user for payment details |
| [`PAYMENT_DECLINED`](/errors/PAYMENT_DECLINED) | Declined by issuer | Use a different payment method |

#### Order Errors

| Code | Description | Action |
|------|-------------|--------|
| [`ORDER_TOTAL_DIFFERENT`](/errors/ORDER_TOTAL_DIFFERENT) | Total changed since validation | Re-validate the order |
| [`ORDER_ALREADY_PLACED`](/errors/ORDER_ALREADY_PLACED) | Idempotency-key conflict | This order was already placed |

#### Open enum: order errors[].code

The `errors[].code` values on an order (both `validateOrder` 200 responses and `placeOrder` 4xx `ProblemDetail` responses) are an **open enum** (`x-extensible-enum`). The set above lists the current values but is 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](/distribution-partners/marketfront-api/guides/order-lifecycle#error-codes).

This is distinct from the top-level `ProblemDetail.errorCode` field (a single string naming the overall failure reason on a 4xx place response).

#### Address Errors

| Code | Description | Action |
|------|-------------|--------|
| [`ADDRESS_INVALID`](/errors/ADDRESS_INVALID) | Could not be validated | Verify the address |
| [`ADDRESS_OUT_OF_RANGE`](/errors/ADDRESS_OUT_OF_RANGE) | Outside store's delivery zone | Choose a different store |

#### Authentication Errors

| Code | HTTP | Description | Action |
|------|------|-------------|--------|
| [`SESSION_USER_REQUIRED`](/errors/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 for `X-RateLimit-Reset`, then retry |
| `500` Internal Error | Yes | Exponential backoff (1s, 2s, 4s, max 30s) |

> Always include the `requestId` when contacting support about a specific error.

## Rate Limits

Per-partner rate limits ensure fair usage and platform stability.

| Endpoint Type | Limit | Examples |
|--------------|-------|---------|
| **Read operations** | 1,000/min | `/stores/discover`, `/catalog-sets/{catalogSetId}` |
| **Write operations** | 100/min | `/orders/validate`, `/orders/place` |
| **Store discovery** | 60/min | `/stores/discover` (also counted under reads) |

Sandbox rate limits are **5x higher** than production.

Rate limits are scoped to your **partner organization** — all keys belonging to the same organization share the same quota.

Every response includes rate limit headers:

| Header | Description |
|--------|-------------|
| `X-RateLimit-Remaining` | Requests remaining in the current window |
| `X-RateLimit-Reset` | Unix timestamp when the window resets |

When rate limited, the API returns `429 Too Many Requests`. Read the `X-RateLimit-Reset` header to know when to retry. If the header is missing, use exponential backoff starting at 1 second.

## 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 |
