---
title: "Errors"
description: "Every REST status code, the error codes you can branch on, and the response bodies."
---

Every error is JSON with a `message`. Some errors also carry a `code` you can branch on. Some carry
`details`.

## Status codes

| Status | Meaning | Body |
| --- | --- | --- |
| 400 | Validation failed, or a user input error | `{ message: "Validation Failed", details: { "body.date": { message, value } } }` |
| 401 | No key, an unknown key, or an expired key | `{ message, code: "UNAUTHENTICATED" }` or `{ message, code: "API_KEY_EXPIRED" }` |
| 403 | A valid key without the scope the route needs | `{ message, code: "INSUFFICIENT_SCOPE" }` |
| 403 | A resource owned by another organization, on most routes | `{ message, code: "FORBIDDEN" }` |
| 404 | Not found, or a theme owned by another organization | `{ message }` |
| 409 | A graphic is locked, a publish conflicts, or a stored graphic asset fails verification | `{ message, code }`; `LOCKED` also includes `holder.userName` |
| 429 | The rate limit is exceeded while enforcement is on | `{ message }` |
| 500 | Unexpected. Retry once, then contact support | `{ message }` |

:::note
A request for a resource in another organization is always refused, but the status is not uniform
today. Theme routes return **404**. Match, team, player, overlay, stream and data source routes
return **403**. Do not read ownership from the status code, and do not treat a **404** as proof that
an id does not exist.
:::

For a 500, send the `traceparent` header value you used to support. It lets LIGR find your request.

## Error codes

| `code` | Status | When | Fix |
| --- | --- | --- | --- |
| `API_KEY_EXPIRED` | 401 | The key passed its expiry date | Create a new key in the dashboard |
| `INSUFFICIENT_SCOPE` | 403 | The key lacks the scope the route needs. The message names the scope | Create a key with that scope |
| `LOCKED` | 409 | Someone has the graphic open in the dashboard editor, or a stale lock has not expired. A lock expires after 60 seconds | Wait, then retry. `holder.userName` names the editor |
| `CONFLICT` | 409 | Two publishes raced | Read the theme again, then publish again |
| `GRAPHIC_ASSET_MISSING` | 409 | A graphic or theme publish references an S3 asset that is missing | Upload the missing asset, then publish again |
| `GRAPHIC_ASSET_INVALID` | 409 | An asset cannot be read, or its stored size or SHA-256 differs from the recorded value | Replace the asset with the intended bytes, then publish again |
| `CODE_GRAPHIC_INVALID` | 400 | An unknown control variable type, or a bundle that names a file you did not upload in the session it names and the graphic does not already hold | Read `details[]`. Each line is one problem. Upload the named file, then record the bundle with the `uploadSessionId` of that upload |
| `BUNDLE_TOO_LARGE` | 400 | The bundle is over 10 MiB, or one file is over 5 MiB | Read `details[]`. It lists the files that are too large |
| `THEME_LIMIT_REACHED` | 409 | Your organization already owns five themes | Delete a theme you no longer use, or ask your LIGR contact |
| `THEME_IN_USE` | 409 | A competition still uses the theme you want to delete | Read `details[]`. Remove the theme instance from each competition, then delete again |
| `GRAPHIC_TYPE_MISMATCH` | 400 | The id belongs to a Rive graphic, and the route serves code graphics | Use the [Rive graphics](/rive-graphics/) routes for a Rive graphic |
| `INVALID_ASSET_PATH` | 400 | A file name holds `..`, an empty segment, a control character, or one of `?`, `#`, `%` | Rename the file |
| `RUNTIME_HAS_NO_EDITABLE_SOURCE` | 400 | A raw `.riv` upload requested source retention | Send `retainSource: false`. The runtime revision preserves those bytes |
| `INVALID_SELECTION` | 422 | The chosen artboard, state machine, or archive entry is unavailable | Read current import metadata, then send an exact returned index or entry |
| `SCRIPT_SIGNING_UNSUPPORTED` | 422 | Source requires script signing, which this converter cannot perform | Export a signed `.riv` externally, then upload the runtime |
| `IMPORT_UNAVAILABLE` | 503 | Source import is disabled or not qualified in this environment | Read the capability response and use an available path |

## Validation errors

A 400 from schema validation names each bad field.

```json title="400 Bad Request"
{
  "message": "Validation Failed",
  "details": {
    "body.date": {
      "message": "invalid ISO 8601 date",
      "value": "12-09-2026"
    }
  }
}
```

## Retries

| Status | Retry? |
| --- | --- |
| 400, 401, 403, 404 | No. Fix the request |
| 409 | For `LOCKED` or `CONFLICT`, retry after resolving the conflict. For `GRAPHIC_ASSET_*`, repair the asset first |
| 429 | Yes, after the `RateLimit-Reset` seconds. See [Rate limits](/get-started/rate-limits/) |
| 500 | Once. Then contact support |

Make write requests safe to repeat. Read the entity back before you write it again.