# Erreurs

> Un refus porte toujours un code stable sur lequel aiguiller. Le même code traverse REST, gRPC, MCP et les canaux asynchrones. Côté produit : Passerelle d'API…

Référence - Depuis la version 0.1.0 - Flexway v1.6.3 - https://flexway.io/docs/developer-errors/

> Un refus porte toujours un `code` stable sur lequel aiguiller. Le même `code` traverse REST, gRPC, MCP et les canaux asynchrones.
>
> Côté produit : [Passerelle d'API](https://flexway.io/docs/product-api-gateway/) ; côté exploitation : [Dépannage](https://flexway.io/docs/operations-troubleshooting/).

## Le 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"
}
```

Aiguillez sur `code` (ou sur `type`, que l'exploitant peut rediriger vers son portail), jamais sur
`title` ou `detail`. Une erreur ne contient jamais le message interne de l'API, un nom d'hôte ni une
donnée personnelle.

## Les codes

| Statut | `code` | À qui d'agir |
| --- | --- | --- |
| `400` | `malformed_request` | Vous : requête illisible ou paramètre non déclaré |
| `401` | `unauthenticated` | Vous : identifiant absent ou invalide |
| `403` | `forbidden` | Vous : portée, rôle ou tenant insuffisant |
| `404` | `not_found` | Vous : route non déclarée |
| `405` | `method_not_allowed` | Vous : voir `Allow` |
| `412` | `precondition_failed` | Vous : `If-Match` non satisfait, ou reprise SSE hors historique |
| `413` | `payload_too_large` | Vous |
| `415` | `unsupported_media_type` | Vous : voir `Accept` |
| `422` | `invalid_request` | Vous : voir `errors` |
| `428` | `idempotency_key_required` | Vous : ajouter `Idempotency-Key` |
| `429` | `rate_limited` | Vous : ralentir, voir `Retry-After` |
| `429` | `quota_exceeded` | Vous : quota du jour ou du mois dépensé |
| `500` | `internal_error` | La passerelle |
| `502` | `upstream_error` | L'API |
| `502` | `response_too_large` | L'API |
| `503` | `upstream_unavailable` | L'API : réessayer après `Retry-After` |
| `503` | `overloaded` | La plateforme : réessayer après `Retry-After` |
| `504` | `upstream_timeout` | L'API : issue inconnue pour une écriture |
| selon l'API | `upstream_status` | L'API : erreur d'un amont gRPC, ou statut traduit par l'exploitant |

`400` dit « je n'ai pas pu lire », `422` « j'ai lu, et c'est faux » ; `429` est **votre** débit,
`503` **notre** capacité ; `502` une réponse inexploitable, `504` pas de réponse à temps.

## Sur les autres surfaces

| Surface | Représentation |
| --- | --- |
| `/v1/*` (inférence) | Enveloppe OpenAI : `unauthenticated` -> `authentication_error`, `forbidden` -> `permission_error`, `rate_limited` -> `rate_limit_error`, `invalid_request` -> `invalid_request_error` ; le `code` de la passerelle est gardé ([inférence](https://flexway.io/docs/developer-inference/)) |
| gRPC | `google.rpc.Status`, `ErrorInfo.reason` = le `code` ([gRPC](https://flexway.io/docs/developer-grpc/)) |
| MCP | Résultat `isError` portant le problème ([MCP](https://flexway.io/docs/developer-mcp/)) |
| SSE, WebSocket | `event: error` ou dernier message, puis fermeture ([canaux asynchrones](https://flexway.io/docs/developer-async/)) |
