# Error codes

> Every error code with its HTTP status, fixed message and what to do; 402 versus 429; the three error formats.

When a request fails, the HTTP status and `code` tell you what happened. Match on `code`, not on the message text.

## Error formats

OpenAI-format and query endpoints return the OpenAI error object, `/v1/messages` returns Anthropic's and the Gemini endpoints return Gemini's. The examples below show the same 500 `upstream_timeout` in all three.

**OpenAI**

```json
{
  "error": {
    "message": "Model processing timed out.",
    "type": "api_error",
    "param": null,
    "code": "upstream_timeout"
  }
}
```

**Anthropic**

```json
{
  "type": "error",
  "error": {
    "type": "timeout_error",
    "message": "Model processing timed out.",
    "code": "upstream_timeout"
  }
}
```

**Gemini**

```json
{
  "error": {
    "code": 500,
    "message": "Model processing timed out.",
    "status": "DEADLINE_EXCEEDED"
  }
}
```

> **Note**
>
> The Gemini format has no `code` field: use the HTTP status and `status`. Every response, success or error, carries an `x-robi-request-id` header; include it when you contact support.

## All error codes

Fixed messages are exact English sentences. Other messages may change and are only meant for people to read.

| HTTP | code | Fixed message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | `invalid_request` | The request was rejected by the model as invalid. | A parameter is invalid. If the model rejected it, the message is this sentence, or `Invalid value for parameter <param>.` with `param` set. | Fix the request; do not retry it unchanged. |
| `400` | `unsupported_parameter` | Varies | The request uses something Relayvia does not offer: stored server-side state, a hosted tool or a remote file. `param` names it. | Remove the field or send the content inline; see the differences on each endpoint's page. |
| `400` | `invalid_media` | Unsupported input media format. | The model reports that an input image or file format is not supported. | Convert it to a supported format. |
| `400` | `media_unreachable` | Unable to access the input media URL. | The model could not read the media URL you sent. | Make sure the URL is public, or send the media inline. |
| `400` | `content_policy` | Input content did not pass safety review. | The input did not pass the model's safety review. If the model does not say whether the input or the output was refused, the message is `Content did not pass safety review.` | Change the input; the same input fails again. |
| `400` | `content_policy_output` | Generated content did not pass safety review. | The model generated a result, but the result did not pass safety review. Your input was not the problem. | Rephrase the request or adjust parameters, then try again. |
| `401` | `invalid_api_key` | Varies | The API key does not exist, is disabled or has expired. | Check the key in the console, or create a new one. |
| `402` | `insufficient_quota` | Varies | Not enough credits: the account's credits, the key's limit, the team member's limit or an account spend limit cannot cover the request, or the account is frozen. | Top up or raise the limit; retrying does not help. |
| `403` | `forbidden` | Varies | The key may not use this model, the source IP is not on the allowlist, or the account is disabled. | Check the key's allowed models and IP allowlist. |
| `404` | `model_not_found` | Varies | The model is not listed. | Use a name from `GET /v1/models`. |
| `404` | `not_found` | Varies | The path does not exist, for example a base URL with /v1 added twice. | Check the base URL and the path; see [Authentication](https://relayvia.dev/docs/authentication). |
| `413` | `request_too_large` | The input exceeds the model's limit. | The request body is over 20 MB, or the model reports that the input exceeds its context or size limit (then with this sentence). | Shorten or split the input. |
| `429` | `rate_limited` | Varies | Too many requests: per IP, per key, per account, or too many requests in progress at once. | Wait the seconds in `Retry-After`, then retry. |
| `500` | `model_price_error` | Varies | The model has no price yet, so it cannot be called. | Choose another model or contact support. |
| `500` | `internal_error` | Varies | Something went wrong inside Relayvia; the details are only in our logs. | Retry later; if it keeps happening, contact support with the `x-robi-request-id`. |
| `500` | `upstream_error` | Model service is temporarily unavailable. | Relayvia tried other model services and every attempt failed. | Retry later with backoff; the request was already retried, so do not retry in a tight loop. |
| `503` | `no_available_channel` | Varies | No model service is available for this model right now. | Retry later or choose another model. |
| `503` | `service_busy` | The service is currently busy. | Every model service for this model reported being busy. | Wait the seconds in `Retry-After`, then retry. |
| `503` | `service_unavailable` | Varies | Relayvia's own storage is briefly unavailable; the response carries `Retry-After: 1`. | Wait the seconds in `Retry-After`, then retry. |
| `500` | `upstream_timeout` | Model processing timed out. | Every attempt timed out. | Retry later; use streaming for long outputs. |

## 402 is not 429

> **Warning** 402 insufficient_quota: out of credits
>
> The account's credits, the key's credit limit, the team member's credit limit or an account spend limit cannot cover the request, or the account is frozen because its credits went below zero. The request was not sent to the model. Retrying will not help: top up, or ask an admin to raise the limit. The reservation includes your output limit, so a very large `max_tokens` can also cause a 402; lower it.

> **Note** 429 rate_limited: too many requests
>
> You sent requests faster than a rate limit allows. Nothing was charged. Wait the number of seconds in `Retry-After` and retry; most SDKs do this for you.

## Retry-After

429 `rate_limited`, 503 `service_busy` and 503 `service_unavailable` carry a `Retry-After` header in seconds. For `service_busy` it is the longest wait any model service asked for, or 5 seconds by default; for `service_unavailable` it is 1. Wait at least that long before retrying.

```http
HTTP/1.1 503 Service Unavailable
Retry-After: 5
x-robi-request-id: 0193f2c1-7a4e-7cc2-9b1d-5f0e2a6c8d41

{"error":{"message":"The service is currently busy.","type":"api_error","param":null,"code":"service_busy"}}
```

## Errors after retries

When a model service fails, Relayvia retries the request on other services for the same model before answering. If every attempt fails, the error depends on how they failed:

- Every attempt was busy: 503 `service_busy` with `Retry-After`.
- Every attempt timed out: 500 `upstream_timeout`.
- Any other mix: 500 `upstream_error`.

Both of these come back as HTTP 500, so tell them apart by `code`; in the Gemini format, which has no `code`, by `status` (`DEADLINE_EXCEEDED` for a timeout, `INTERNAL` otherwise). SDKs retry them as usual.

## Errors during streaming

Once the first byte is sent the status stays 200. If the model fails mid-stream, you receive an error event in the endpoint's format and the stream ends; Relayvia does not retry after that point. The same applies to non-streaming requests sent with `X-Robi-Keepalive: 1`: a failure arrives as an error object in the body.

## Safety refusals

`content_policy` means the input was refused; `content_policy_output` means the generated result was refused, not your input. A chat model that declines to answer is a normal response, for example Gemini `finishReason: SAFETY` or Anthropic `stop_reason: refusal`, and is passed through as is.
