> This page is for Documentation.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://fiftytwo-demo.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://fiftytwo-demo.ferndocs.com/_mcp/server.

# Errors and retries

The 52ViKING APIs are asynchronous where it matters and rate limited
throughout. Both facts shape how you should handle failures.

## Status codes

| Code                    | Meaning                                      | What to do                                                              |
| ----------------------- | -------------------------------------------- | ----------------------------------------------------------------------- |
| `200`                   | Success with a body                          | Nothing                                                                 |
| `202 Accepted`          | Master Data write queued                     | Poll the `Location` header until the resource reports a terminal status |
| `400`                   | Schema validation failed                     | Fix the payload; retrying will not help                                 |
| `401`                   | Expired token **or** insufficient privileges | Refresh once and replay; if it repeats, check permissions               |
| `413 Payload Too Large` | Master Data body over 2 MB                   | Split the batch                                                         |
| `429 Too Many Requests` | Rate limit exceeded                          | Back off, then retry                                                    |
| `5xx`                   | Server-side fault                            | Retry with exponential backoff and jitter                               |

## Rate limits

> **Warning**
>
> Limits are published as approximate and subject to change. Treat these as
> order-of-magnitude, and always handle `429` rather than assuming you are under
> the ceiling.

| API               | Documented limit                                      |
| ----------------- | ----------------------------------------------------- |
| Auth (`/v1/user`) | 15 sign-ins per 60 seconds                            |
| Master Data       | \~3 requests/second, measured over a 10-second window |

The Master Data guidance is explicit and worth repeating: **use the 2 MB body
allowance rather than sending many small updates.** A single POST carrying a
thousand articles is cheaper for both sides than a thousand POSTs, and it is far
less likely to hit `429`.

## Asynchronous writes

Two APIs accept work and complete it later. Neither tells you the outcome in the
response to your write.

### Master Data

Every `POST` returns `202 Accepted` with a reference in the `Location` header.
Schema validation is synchronous, so a malformed body fails immediately with
`400`. The distribution to stores is not.

#### POST the batch

You get `202` and a location. This means "accepted for processing", not
"applied".

#### Poll the location

`GET` it to read the current status of the backend process.

#### Stop on a terminal status

Poll with backoff, and give up after a bounded number of attempts rather
than forever.

> **Note**
>
> Chain-scoped and store-scoped POSTs behave differently. Specify only the chain
> and the enterprise controller distributes identical data to every store.
> Specify chain **and** store and the data is store-specific, handled by that
> store directly. If you are unsure which you want, ask your FiftyTwo
> consultant. The choice is difficult to reverse once data is out.

### Register Sale

`POST /v1/sale` returns a correlation ID immediately, and the sale starts in
`Pending`. Poll `GET /v1/sale/{correlationId}` until it reports `Processed` or
`Failed`.

```python
import time

def wait_for_sale(client, correlation_id, timeout=120):
    """Poll until the sale reaches a terminal status."""
    deadline = time.monotonic() + timeout
    delay = 1.0
    while time.monotonic() < deadline:
        status = client.get_sale(correlation_id)["status"]
        if status in ("Processed", "Failed"):
            return status
        time.sleep(delay)
        delay = min(delay * 1.6, 10.0)   # back off, but keep checking
    raise TimeoutError(f"sale {correlation_id} still pending after {timeout}s")
```

## A retry policy that works here

#### Never retry a 400

Validation failures are deterministic. Log the payload and move on.

#### Retry a 401 exactly once, after refreshing

More than once and you are probably looking at a permissions problem, not an
expiry problem.

#### Back off on 429 and 5xx

Exponential backoff with jitter, so a fleet of workers does not resynchronise
into a thundering herd after an outage.

#### Make writes idempotent on your side

Master Data POSTs are insert-or-update by `object_id`, so replaying a batch
is safe. Register Sale is not inherently idempotent, so track correlation
IDs to stop a retry after a network timeout importing the sale twice.

> **Warning**
>
> Duplicate `object_id` values **within a single Master Data POST body** are
> handled according to rules described in the
> [Master Data overview](/api/master-data/overview). Deduplicate before you send
> rather than relying on that behaviour.