---
title: "Errors"
description: "The structure of an error response, and what to do with it."
url: https://docs.roasos.com/concepts/errors
---

# Errors

All errors have the same structure. Your software can therefore process all errors in the same way.

```json
{
  "error": {
    "code": "invalid_api_key",
    "message": "Invalid or revoked API key",
    "retryable": false,
    "request_id": "7501d15d-b189-40ea-b8fa-629cf4722618"
  }
}
```

## The fields

* **`code`** — a stable code for your software. Use this field in your conditions. Do not use the `message` field.
* **`message`** — an explanation for a person. The text can change. Do not parse the text.
* **`retryable`** — the value `true` shows that the same request can be successful later. The value `false` shows that you must first correct the request.
* **`request_id`** — the identifier of the request. Give this identifier to the support staff.

## HTTP status codes

| Status | Meaning                                                                                 |
| ------ | --------------------------------------------------------------------------------------- |
| `400`  | The request contains data that is not valid.                                            |
| `401`  | The API key is absent or not valid.                                                     |
| `402`  | Your workspace does not have sufficient credits.                                        |
| `403`  | Your key does not have permission for this action, or the price is above a spend bound. |
| `404`  | roasOS cannot find the item in your workspace.                                          |
| `409`  | The item exists, and its state refuses this action.                                     |
| `410`  | The quote expired. Quote the request again.                                             |
| `429`  | You sent too many requests. Decrease the rate.                                          |
| `5xx`  | An internal error occurred. Send the request again if `retryable` is `true`.            |

## Codes on a paid request

These codes belong to the quote and confirm flow. Refer to [Quotes and credits](/concepts/quotes-and-credits).

| Code                             | Status | Cause                                                                 |
| -------------------------------- | ------ | --------------------------------------------------------------------- |
| `idempotency_key_required`       | `400`  | The `Idempotency-Key` header is absent.                               |
| `invalid_idempotency_key`        | `400`  | The header value is not 16 to 255 characters of the allowed set.      |
| `quote_required`                 | `400`  | The body carries no `quote_only` and no `quote_id`.                   |
| `quote_expired`                  | `410`  | More than 10 minutes passed since the quote.                          |
| `insufficient_credits`           | `402`  | The workspace balance is below the price.                             |
| `request_credit_limit_exceeded`  | `403`  | The price is above `max_credits_per_request` on your key.             |
| `daily_credit_budget_exceeded`   | `403`  | The daily budget of the key is used.                                  |
| `monthly_credit_budget_exceeded` | `403`  | The monthly budget of the key is used.                                |
| `spend_approval_required`        | `403`  | The price is above the approval threshold, and no person approved it. |
| `rate_limit_exceeded`            | `429`  | The key sent too many requests.                                       |

## `409` — the state refuses the action

A `409` is a state answer, and it is never a fault in your body. Three are common.

| Code                        | Cause                                                  | What to do                                             |
| --------------------------- | ------------------------------------------------------ | ------------------------------------------------------ |
| `run_not_completed`         | You read `/result` before the job is terminal.         | Poll `/status` first. Refer to [Jobs](/concepts/jobs). |
| `job_not_cancelable`        | You canceled a job that is already terminal.           | Read the job. It finished.                             |
| `research_result_not_ready` | You read a research result before the job is terminal. | Poll the status of the research job.                   |

## `503` — a store that cannot answer

The quotes, the memory, the brand kit, the intelligence, the skills and the idempotency records each have their own store. When one store cannot answer, the request stops with a `503`, and the code names the store. This is a published shape, and it is not a fault in your request.

The `retryable` field splits the family in two. Read it, and do not assume that a `503` is always transient.

| `retryable` | Codes                                                                                                                                                                                                                                                 |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `true`      | `idempotency_store_unavailable`, `generation_quote_store_unavailable`, `research_quote_store_unavailable`                                                                                                                                             |
| `false`     | `brand_kit_store_unavailable`, `memory_store_unavailable`, `intelligence_store_unavailable`, `skill_store_unavailable`, `generation_batch_quote_store_unavailable`, `media_rights_attestation_store_unavailable`, `billing_command_store_unavailable` |

<Callout type="warn">
  A `503` with `retryable: false` means that roasOS cannot prove what the request did. Read the state before you send the request again. Send it with the same `Idempotency-Key`, so a paid action that did run charges you one time only.
</Callout>

## Send a request again safely

If `retryable` is `true`, send the request again with the &#x2A;*same `Idempotency-Key`**. roasOS then charges you one time only.

Increase the delay between the attempts. For example, wait 1 second, then 2 seconds, then 4 seconds.

A paid request can stop with a timeout. You then do not know if the action ran. Send the request again with the same key. If the action ran, roasOS gives you the first result. If the action did not run, roasOS runs it.
