# Errors

> A refusal always carries a stable code to switch on. The same code crosses REST, gRPC, MCP and the asynchronous channels. Product side: API gateway…

Reference - Since version 0.1.0 - Flexway v1.6.3 - https://flexway.io/en/docs/developer-errors/

> A refusal always carries a stable `code` to switch on. The same `code` crosses REST, gRPC, MCP and the asynchronous channels.
>
> Product side: [API gateway](https://flexway.io/en/docs/product-api-gateway/); operations side: [Troubleshooting](https://flexway.io/en/docs/operations-troubleshooting/).

## The format

```json
{
  "type": "https://flexway.dev/problems/upstream_timeout",
  "title": "The upstream did not answer within its budget",
  "status": 504,
  "detail": "Operation getOrder exceeded its 2s budget.",
  "instance": "/orders/42",
  "code": "upstream_timeout",
  "request_id": "01J9Z2K7QWE",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
```

Switch on `code` (or on `type`, which the operator may redirect to its portal), never on `title` or
`detail`. An error never holds the API's internal message, a host name or personal data.

## The codes

| Status | `code` | Who acts |
| --- | --- | --- |
| `400` | `malformed_request` | You: unreadable request or undeclared parameter |
| `401` | `unauthenticated` | You: credential missing or invalid |
| `403` | `forbidden` | You: scope, role or tenant insufficient |
| `404` | `not_found` | You: undeclared route |
| `405` | `method_not_allowed` | You: see `Allow` |
| `412` | `precondition_failed` | You: `If-Match` not met, or SSE resumption beyond the history |
| `413` | `payload_too_large` | You |
| `415` | `unsupported_media_type` | You: see `Accept` |
| `422` | `invalid_request` | You: see `errors` |
| `428` | `idempotency_key_required` | You: add `Idempotency-Key` |
| `429` | `rate_limited` | You: slow down, see `Retry-After` |
| `429` | `quota_exceeded` | You: the daily or monthly quota is spent |
| `500` | `internal_error` | The gateway |
| `502` | `upstream_error` | The API |
| `502` | `response_too_large` | The API |
| `503` | `upstream_unavailable` | The API: retry after `Retry-After` |
| `503` | `overloaded` | The platform: retry after `Retry-After` |
| `504` | `upstream_timeout` | The API: unknown outcome for a write |
| per the API | `upstream_status` | The API: an error of a gRPC upstream, or a status the operator translated |

`400` says "I could not read it", `422` "I read it, and it is wrong"; `429` is **your** rate, `503`
**our** capacity; `502` an unusable answer, `504` no answer in time.

## On the other surfaces

| Surface | Representation |
| --- | --- |
| `/v1/*` (inference) | OpenAI's envelope: `unauthenticated` -> `authentication_error`, `forbidden` -> `permission_error`, `rate_limited` -> `rate_limit_error`, `invalid_request` -> `invalid_request_error`; the gateway's `code` is kept ([inference](https://flexway.io/en/docs/developer-inference/)) |
| gRPC | `google.rpc.Status`, `ErrorInfo.reason` = the `code` ([gRPC](https://flexway.io/en/docs/developer-grpc/)) |
| MCP | An `isError` result carrying the problem ([MCP](https://flexway.io/en/docs/developer-mcp/)) |
| SSE, WebSocket | `event: error` or a last message, then the close ([asynchronous channels](https://flexway.io/en/docs/developer-async/)) |
