> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livry.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Public API Errors: Status Codes and Causes

> Every failure the Livry Public API returns, the machine-readable cause beside it, and what a client should do about each one.

Every `4xx` and `5xx` carries the same JSON body.

```json theme={null}
{
  "status": "error",
  "cause": "validation",
  "messages": [
    "'slug' must be at most 64 characters.",
    "'displayName' must not be empty."
  ]
}
```

`cause` is the field to branch on. `messages` is an array because a validator reports every failure
at once — collapsing it to the first would make you fix one field per round trip.

<Note>
  **`messages` is empty for everything a flow refused**, as opposed to everything the request shape
  refused. A domain failure explaining itself to a stranger is a domain failure leaking what exists,
  so a `403` and a `404` say only what their status already says. The `cause` is there precisely so
  you do not have to parse prose that is not going to be written.
</Note>

## The table

| Status | `cause` | What happened | What to do |
| - | - | - | - |
| `400` | `payload` | The body was not JSON, or a URL parameter would not convert — `?pageSize=abc`. | Fix the request. `messages` names the parameter. |
| `400` | `validation` | The request parsed but broke a rule. | Fix the fields `messages` names. |
| `401` | *(no body)* | No token, an expired token, or a token for another audience. | Mint a token for `https://api.livry.dev/public/v1`. |
| `402` | `planLimit` | The Team's plan allows no more of what you were creating. | Upgrade the plan. **Retrying will not help.** |
| `403` | `forbidden` | You are in the Team but hold no grant on that Environment, or not a high enough one. | Ask an Admin for the grant. |
| `404` | `notFound` | The resource does not exist, **or** it belongs to a Team you are not in. | The two are deliberately indistinguishable. |
| `409` | `conflict` | The state you were writing against has moved — a stale `etag`, or a slug somebody else just took. | Re-read and retry. |
| `412` | `conflict` | Same meaning as `409`. | Same: re-read and retry. |
| `500` | `internal` | Ours. | Retry with backoff; tell us if it persists. |

`409` and `412` share a cause on purpose. Both mean "the thing you were writing against has moved",
and the client's response to either is identical.

## `402 planLimit` is not a rate limit

It is a subscription ceiling — see [Plans and limits](/settings/billing). From this API it means one
thing: **creating a Variant past the Theme's allowance.**

```json theme={null}
{
  "status": "error",
  "cause": "planLimit",
  "messages": []
}
```

Treat it as terminal for that request and surface "upgrade required" rather than queueing a retry.

<Warning>
  **Enforcement counts, then creates.** Two creates arriving at the same instant can leave a Team one
  over its allowance. That is accepted behaviour, not a bug to report.
</Warning>

## `409` on a Variant publish

There is one `409` that re-reading will not clear: **a Variant can be published only once per Theme
version.**

A Variant's version number *is* the Theme version its overrides are addressed against. Publishing a
Variant writes a version file numbered with the Theme's current version; publishing it a second time
against the same Theme version finds that file already written and answers `409`.

To publish a Variant change again, publish the Theme first. See
[Versioning](/theming/versioning).

## Concurrency in practice

<Steps>
  <Step title="Read the token set">
    ```bash theme={null}
    curl "$BASE/environments/$ENV/themes/$THEME/tokens" \
      -H "Authorization: Bearer $ACCESS_TOKEN"
    ```

    The response carries `etag`. It is `null` when there is no draft, which is normal.
  </Step>

  <Step title="Send it back with the write">
    ```bash theme={null}
    curl -X PATCH "$BASE/environments/$ENV/themes/$THEME/tokens?etag=$ETAG" \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{ "operations": [ ... ] }'
    ```
  </Step>

  <Step title="On 409, re-read">
    Do not retry the same batch blindly against a moved document. Re-read, decide whether your
    change still applies, and send it again with the new `etag`.
  </Step>
</Steps>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.