# Erreurs

> Les codes d’erreur stables de la bibliothèque, du protocole et de la page de livraison, et quoi en faire.

Canonical: https://colis-docs.vercel.app/docs/erreurs · Markdown: https://colis-docs.vercel.app/docs/erreurs.md

colis a trois familles d’erreurs, chacune avec un `code` stable sur lequel brancher plutôt que sur un
statut HTTP ou le texte d’un message.

## Dans la bibliothèque : `ColisError`

Tout ce que lève `@colis/core` est une `ColisError`, avec un `code` et l’erreur d’origine dans
`cause` :

```ts
import { isColisError } from '@colis/core'

try {
  await store.upload(file)
} catch (error) {
  if (isColisError(error)) console.error(error.code, error.message, error.cause)
  throw error
}
```

| Code                     | Quand                                                                                                      |
| ------------------------ | ---------------------------------------------------------------------------------------------------------- |
| `INVALID_CONFIG`         | Bucket manquant, `publicUrl` qui n’est pas une URL absolue, `maxSize` non positif, forme de code invalide. |
| `INVALID_SYNC_CODE`      | Le code est vide, ou contient un caractère hors de l’alphabet.                                             |
| `INVALID_KEY`            | Clé vide, `/` en début ou en fin, segment `..`, antislash, caractère de contrôle, plus de 1 024 octets.    |
| `INVALID_BODY`           | Le corps envoyé n’est pas d’un type pris en charge.                                                        |
| `MISSING_CONTENT_LENGTH` | Un flux envoyé sans `contentLength`.                                                                       |
| `FILE_TOO_LARGE`         | Le corps dépasse `maxSize`.                                                                                |
| `UPLOAD_FAILED`          | `PutObject` ou un envoi en morceaux a été refusé. Erreur d’origine dans `cause`.                           |
| `GET_FAILED`             | `GetObject` ou `HeadObject` a échoué pour une autre raison qu’une clé absente.                             |
| `DELETE_FAILED`          | `DeleteObject` ou `DeleteObjects` a été refusé, ou S3 a signalé des échecs par clé.                        |
| `URL_FAILED`             | `expiresIn` invalide, échec de signature, ou URL publique demandée sans `publicUrl`.                       |
| `PRECONDITION_FAILED`    | Une écriture `ifMatch` ou `ifAbsent` a perdu la course.                                                    |
| `INVALID_SNAPSHOT`       | Un enregistrement JSON illisible, ou des données que JSON ne sait pas représenter.                         |
| `SNAPSHOT_TOO_NEW`       | Un enregistrement écrit par un schéma plus récent que `maxVersion`.                                        |

`INVALID_CONFIG`, `INVALID_SYNC_CODE`, `INVALID_KEY`, `INVALID_BODY`, `MISSING_CONTENT_LENGTH` et
`FILE_TOO_LARGE` sont levées avant qu’un seul octet ne parte : un code absurde collé dans un champ
coûte une comparaison, pas une requête.

Une clé absente n’est pas une erreur : `get()`, `head()` et `getSnapshot()` renvoient `null`, et
`delete()` sur une clé absente réussit sans bruit.

## Sur le fil : `TransferError`

Les routes du [protocole](https://colis-docs.vercel.app/docs/protocole#les-erreurs) répondent `{ error: { code, message } }`, et
le client de `@colis/protocol` (donc la CLI, les hooks React et le nœud n8n) lève une
`TransferError` avec ce `code` : `INVALID_REQUEST`, `INVALID_SYNC_CODE`, `UNAUTHORIZED`, `NOT_FOUND`,
`CODE_TAKEN`, `SNAPSHOT_TOO_NEW`, `TOO_LARGE`, `INTERNAL`.

```ts
import { isTransferError } from '@colis/protocol'

if (isTransferError(error) && error.code === 'NOT_FOUND') {
  // code inconnu ou expiré : les deux sont indiscernables, exprès
}
```

## Sur la page de livraison

Ses routes propres gardent la même forme de réponse, avec quelques codes en plus :

| Code              | HTTP  | Quand                                                                                                                                                                                |
| ----------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `UNAUTHORIZED`    | `401` | `DROP_PASSWORD` manquant ou faux à l’envoi, ou colis protégé sans son mot de passe. L’en-tête `x-drop-protected: 1` distingue le second cas.                                         |
| `FORBIDDEN`       | `403` | L’expéditeur essaie de répondre à la place du client.                                                                                                                                |
| `NOT_FOUND`       | `404` | Code inconnu ou expiré, colis à usage unique déjà téléchargé, ou aucun accusé.                                                                                                       |
| `ALREADY_DECIDED` | `409` | Le colis a déjà reçu une réponse ; elle est définitive.                                                                                                                              |
| `TOO_LARGE`       | `413` | Fichier au-delà de `DROP_MAX_SIZE_MB` en une requête, ou de `DROP_MAX_UPLOAD_MB` en morceaux (`DROP_OPEN_MAX_UPLOAD_MB` sans mot de passe).                                          |
| `RATE_LIMITED`    | `429` | Trop de mauvais mots de passe (le code est verrouillé), ou, sans `DROP_PASSWORD`, trop d’envois depuis une adresse (`DROP_UPLOADS_PER_HOUR`). `retry-after` dit combien de secondes. |

Les routes des [comptes](https://colis-docs.vercel.app/docs/compte) ajoutent les leurs :

| Code                  | HTTP  | Quand                                                                                                             |
| --------------------- | ----- | ----------------------------------------------------------------------------------------------------------------- |
| `ACCOUNTS_DISABLED`   | `503` | Le déploiement n’a pas d’e-mail configuré (`RESEND_API_KEY`, `COLIS_MAIL_FROM`) : personne ne se connecte.        |
| `UNAUTHENTICATED`     | `401` | Aucune session sur `GET /api/me`, ou confirmation d’un code de terminal sans être connecté.                       |
| `INVALID_SESSION`     | `401` | Un jeton `Bearer colis_…` inconnu, expiré ou révoqué, y compris sur un envoi : jamais d’envoi anonyme à la place. |
| `INVALID_LINK`        | `400` | Un lien de connexion déjà utilisé ou expiré (15 minutes).                                                         |
| `INVALID_DEVICE_CODE` | `400` | Un code de terminal inconnu, expiré (10 minutes) ou déjà échangé.                                                 |
| `FORBIDDEN_ORIGIN`    | `403` | Une requête qui modifie la session depuis un autre site que le déploiement.                                       |
| `MAIL_FAILED`         | `502` | Resend a refusé l’e-mail, ou n’a pas répondu.                                                                     |

La [formule Pro](https://colis-docs.vercel.app/docs/abonnement) ajoute les siens :

| Code                | HTTP  | Quand                                                                                                                                                  |
| ------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `PLAN_LIMIT`        | `403` | Un envoi au-delà de la formule gratuite que Pro accepterait : fichier trop gros, ou durée trop longue. `details` dit laquelle : `{ limit, max, pro }`. |
| `BILLING_DISABLED`  | `503` | Le déploiement ne vend pas Pro : une des quatre variables `STRIPE_*` manque, ou les comptes sont désactivés.                                           |
| `ALREADY_PRO`       | `409` | Un paiement demandé pour un compte déjà Pro.                                                                                                           |
| `NO_SUBSCRIPTION`   | `404` | L’espace d’abonnement demandé pour un compte qui n’a jamais souscrit.                                                                                  |
| `BILLING_FAILED`    | `502` | Stripe a refusé la demande, ou n’a pas répondu.                                                                                                        |
| `INVALID_SIGNATURE` | `400` | Un webhook dont la signature `Stripe-Signature` ne correspond pas, ou date de plus de cinq minutes.                                                    |

```json
{
  "error": {
    "code": "PLAN_LIMIT",
    "message": "Ce fichier dépasse 100 Mo, la limite de la formule gratuite. La formule Pro accepte jusqu’à 2 Go.",
    "details": { "limit": "size", "max": 104857600, "pro": 2147483648 }
  }
}
```

Les messages de ces routes sont en français, prêts à être affichés au client.

## Dans la CLI

La CLI sort avec le code `1` à chaque échec, et distingue ce qu’il faut faire :

- **une erreur d’usage** (commande inconnue, fichier absent, bucket non configuré) s’affiche avec une
  piste de correction en dessous ;
- **une erreur du protocole ou de la bibliothèque** s’affiche avec son code, par exemple
  `NOT_FOUND: Unknown or expired code.` ;
- **`INVALID_CONFIG`** rappelle que `colis config` montre ce que la configuration a résolu ;
- **`PLAN_LIMIT`** rappelle que la formule Pro va plus loin : `colis compte pro`.

En cas de doute, `colis verifier` effectue chaque opération et dit laquelle échoue.
