---
seo_title: Codes d'erreur API : 400, 401, 402, 403, 413 | EnConvert
meta_desc: Tous les codes de statut HTTP de l'API EnConvert : 401 clé API invalide, 402 quota d'ops atteint, 413 fichier trop volumineux, messages exacts et correctifs.
keywords: codes erreur api enconvert, erreur 402 quota ops mensuel api, erreur 401 clé api invalide x-api-key, erreur 413 fichier trop volumineux api, différence 402 et 403 api, erreur 422 champ inconnu api, erreur 429 trop de requêtes api, format json réponse erreur api, erreur 410 artefact expiré, erreur 503 convertisseur indisponible
---

# Codes d'erreur de l'API EnConvert

Cette référence répertorie tous les codes de statut HTTP et messages d'erreur renvoyés par l'API EnConvert, de `200 OK` pour les conversions synchrones et `202 Accepted` pour les tâches async et batch, jusqu'aux réponses d'erreur documentées ci-dessous. Chaque section d'erreur liste les chaînes de message exactes, la condition qui déclenche chacune d'elles, et comment corriger la requête. Les corps d'erreur n'ont pas tous la même forme : il en existe six, et la section [Format des réponses d'erreur](#error-response-format) les présente toutes.

Une tâche qui échoue *après* avoir été acceptée n'est pas une erreur HTTP. Le `202` reste valable et l'échec apparaît dans la charge utile de statut de la tâche lorsque vous l'interrogez, comme décrit dans [Tâches synchrones et asynchrones](/fr/docs/concepts/sync-and-async.md).

---

## Codes de statut HTTP

| Code | Statut | Description |
|------|--------|-------------|
| `200` | OK | Conversion terminée avec succès (mode synchrone). |
| `202` | Accepted | La tâche en lot ou asynchrone a été acceptée pour un traitement en arrière-plan. |
| `400` | Bad Request | Paramètres invalides, champs obligatoires manquants, corps de requête mal formé ou contenu de fichier invalide. |
| `401` | Unauthorized | Clé API ou jeton JWT manquant, invalide ou expiré. |
| `402` | Payment Required | Quota mensuel d'ops épuisé, aucune période de facturation active, plafond de watchers atteint, limite de stockage atteinte, ou endpoint V2 désactivé sur votre plan. |
| `403` | Forbidden | Restriction de type de clé, de domaine ou de liste d'endpoints autorisés, restriction de fonctionnalité V1 (async, webhooks, sortie ZIP, authentification de base, lot), ou accès à la ressource d'un autre projet. |
| `404` | Not Found | La ressource demandée (tâche, lot, opération, fichier, watcher ou widget) n'existe pas, ou le chemin n'est pas une route. |
| `405` | Method Not Allowed | Le chemin existe, mais pas pour la méthode HTTP que vous avez utilisée. |
| `409` | Conflict | Un `job_id` fourni par le client est déjà utilisé, ou une nouvelle tentative de webhook a été demandée sur une tâche d'ingestion non terminée. |
| `410` | Gone | Un artefact V2 ou une archive de lot a dépassé la fenêtre de rétention de fichiers de votre plan et n'est plus dans le stockage. |
| `413` | Payload Too Large | Le fichier téléversé dépasse la limite de taille de votre plan d'abonnement. |
| `415` | Unsupported Media Type | L'URL cible a renvoyé un contenu que ce convertisseur ne peut pas rendre (par ex. du JSON vers `url-to-pdf`). |
| `422` | Unprocessable Entity | Le corps de la requête a échoué à la validation de schéma (y compris des champs inconnus sur les endpoints V2), ou une précondition de rendu a échoué, par exemple un `wait_for_selector` qui n'est jamais apparu. |
| `429` | Too Many Requests | Une limite de débit de requêtes sur courte fenêtre a été déclenchée. Ce n'est pas le code de quota : l'épuisement du quota mensuel répond `402`. |
| `500` | Internal Server Error | Erreur inattendue pendant la conversion (notre moteur a rencontré une défaillance). |
| `502` | Bad Gateway | Le site cible n'a pas pu être atteint, un fournisseur en amont a échoué, ou la cible a renvoyé un défi anti-bot sans contenu de page (`/v2/perceive`, sauf si `allow_degraded` est défini). |
| `503` | Service Unavailable | Un convertisseur est indisponible, le pool de rendu ou le portillon d'admission des conversions est saturé, ou une dépendance en amont est en panne. |
| `504` | Gateway Timeout | Le site cible a mis trop de temps à répondre ou à terminer son chargement, ou la requête a dépassé le budget de 300 secondes de la passerelle. |

---

## Format des réponses d'erreur {: #error-response-format }

Il existe six formes de corps. Celle que vous recevez dépend de l'endroit où la défaillance s'est produite, pas du seul code de statut : vérifiez le type de `detail` avant de le lire.

**1. `detail` sous forme de chaîne.** Le cas courant, et la seule forme que voient la plupart des intégrations.

```json
{
    "detail": "Authentication required"
}
```

**2. `detail` sous forme d'objet.** Le `413` de taille de fichier lié au plan. L'objet structuré est la valeur de `detail` : lisez `body.detail.max_size`, pas `body.max_size`. Voir [413 Payload Too Large](#413-payload-too-large).

```json
{
    "detail": {
        "error": "File too large",
        "file_size": 10485760,
        "max_size": 5242880,
        "tier": "free",
        "key_type": "private"
    }
}
```

**3. `detail` sous forme de tableau, plus `errors`.** La validation de schéma (`422`) renvoie les deux : `detail` est la sortie brute du validateur, `errors` est un tableau parallèle de chaînes lisibles par un humain. Voir [422 Unprocessable Entity](#422-unprocessable-entity).

**4. Enveloppe de conversion typée.** `{"error", "code", "detail"}`, avec un `code` lisible par une machine. Émise uniquement par les trois endpoints de conversion d'URL V1. Voir [Erreurs de conversion navigateur](#browser-conversion-errors-415-422-502-504).

**5. Exception non gérée.** Un `500` qui ne provient pas d'un convertisseur n'a aucun `detail` :

```json
{
    "error": "Internal server error",
    "event_id": "a1b2c3d4"
}
```

**6. Délai de requête de la passerelle.** Le budget propre de 300 secondes de la passerelle produit un `504` sans `detail` ni `code` :

```json
{
    "error": "Request timeout"
}
```

---

## 400 Bad Request

Renvoyé lorsque la requête contient des paramètres invalides, des champs manquants ou des données mal formées.

### Validation des entrées

| Message | Condition |
|---------|-----------|
| `'url' must be provided` | Champ `url` manquant ou vide sur les endpoints basés sur une URL. |
| `Invalid file format '{ext}' for {endpoint}. Allowed: {list}` | L'extension du fichier téléversé ne correspond pas aux formats acceptés par l'endpoint. |
| `File content does not match the '{endpoint}' input type.` | L'extension a été acceptée, mais les octets magiques du fichier correspondent à un autre format. |
| `Invalid pdf_options: {error}` | JSON mal formé dans le champ de formulaire `pdf_options`. |

### Validation du lot et du mode

| Message | Condition |
|---------|-----------|
| `Public keys only support a single URL input` | Une clé publique/de tableau de bord a tenté d'envoyer plusieurs URL. |
| `output_format=True requires multiple URLs` | Regroupement ZIP demandé avec une seule URL. |
| `direct_download not supported for multiple URLs` | `direct_download=true` avec un tableau d'URL. |
| `direct_download only works in sync mode` | `direct_download=true` combiné avec `async_mode=true`. |

### Validation de l'authentification, des cookies et des en-têtes

| Message | Condition |
|---------|-----------|
| `'auth' must be an object with 'username' and 'password'` | Le paramètre `auth` a une structure incorrecte. |
| `'cookies' must be an array of cookie objects` | `cookies` n'est pas un tableau. |
| `'cookies' array must not exceed 50 entries` | Plus de 50 cookies fournis. |
| `Cookie at index {i} must be an object` | L'entrée de cookie n'est pas un dictionnaire. |
| `Cookie at index {i} must have 'name' and 'value'` | Champs obligatoires manquants dans le cookie. |
| `Cookie at index {i} must have 'domain' or 'url'` | Le cookie est dépourvu à la fois de `domain` et de `url`. |
| `'headers' must be an object of header name/value pairs` | `headers` n'est pas un dictionnaire. |
| `'headers' must not exceed 20 entries` | Plus de 20 en-têtes personnalisés. |
| `Header '{name}' cannot be overridden` | Tentative de définir un en-tête bloqué. L'ensemble bloqué est `host`, `content-length`, `transfer-encoding`, `connection`, `upgrade`, `te`, `trailer`. |
| `Header '{name}' value must be a string` | La valeur de l'en-tête n'est pas une chaîne de caractères. |
| `Cannot use both 'auth' and an 'Authorization' header. Use 'auth' for HTTP Basic Auth or 'headers' for Bearer/custom auth, not both.` | L'objet `auth` et l'en-tête personnalisé `Authorization` ont tous deux été fournis. |

### Sécurité des URL (SSRF)

Chaque endpoint basé sur une URL examine l'`url` cible avant de la récupérer. Ces messages sont renvoyés en `400` lorsque l'URL n'est pas une adresse publique `http(s)`.

| Message | Condition |
|---------|-----------|
| `Only http:// and https:// URLs are supported.` | L'URL utilise un schéma autre que `http` ou `https`. |
| `URLs with embedded credentials are not allowed. Use the 'auth' field for HTTP Basic Auth.` | L'URL intègre un nom d'utilisateur/mot de passe (`https://user:pass@host/`). |
| `URL has no hostname.` | L'URL n'a pas pu être analysée en un hôte. |
| `This hostname is not allowed.` | L'hôte est `localhost` ou un nom d'hôte de métadonnées cloud. |
| `URLs resolving to private or internal addresses are not allowed.` | L'URL est, ou se résout vers, une IP privée, de bouclage, link-local, réservée ou autrement non publique. |
| `Non-standard IP address notation is not allowed.` | L'hôte utilise une notation IP octale, hexadécimale ou entier compact susceptible de se résoudre de manière ambiguë. |
| `Could not resolve hostname '{hostname}'.` | La résolution DNS de l'hôte a échoué. |
| `This URL is blocked by the site's threat policy.` | L'hôte cible figure sur la liste de blocage de la politique de menaces, vérifiée en même temps que le filtre SSRF. |

### Validation des options de rendu

| Message | Condition |
|---------|-----------|
| `'wait_for_selector' must be a string` | `wait_for_selector` n'était pas une chaîne de caractères. |
| `'wait_for_selector' is too long (max 1000 chars)` | Le sélecteur dépasse 1000 caractères. |
| `'wait_for_selector_timeout' must be a positive integer (ms)` | Le délai est manquant, nul, négatif ou n'est pas un entier. |
| `'wait_for_selector_timeout' must not exceed 60000 ms` | Le délai dépasse le plafond de 60 secondes. |
| `'block_ads' must be a boolean` / `'block_media' must be a boolean` | L'indicateur de blocage n'était pas un booléen. |

### Erreurs de sitemap et de crawl

| Message | Condition |
|---------|-----------|
| `No URLs found in sitemap: {url}` | Le sitemap a été analysé mais ne contient aucune URL. |
| `Timeout fetching sitemap: {url}` | La récupération du sitemap a dépassé le délai de 30 secondes. |
| `Could not fetch sitemap: {url} returned {status}` | L'URL du sitemap a renvoyé un statut HTTP différent de 200. |
| `Invalid XML in sitemap: {url}` | Le XML du sitemap n'a pas pu être analysé. |
| `Unrecognized sitemap format at {url}: root element is <{tag}>` | L'élément racine du sitemap n'est ni `<urlset>` ni `<sitemapindex>`. |
| `No pages discovered on {base_url}` | Le crawl complet s'est terminé mais aucune page n'a été trouvée. |

### Erreurs de contenu de conversion

| Message | Condition |
|---------|-----------|
| `Invalid JSON: {error}` | Le fichier JSON contient une syntaxe JSON invalide. |
| `Invalid YAML: {error}` | Le fichier YAML contient une syntaxe YAML invalide. |
| `Invalid TOML: {error}` | Le fichier TOML contient une syntaxe TOML invalide. |
| `Invalid HTML encoding (expected UTF-8)` | Le fichier HTML n'est pas encodé en UTF-8. |
| `Invalid Markdown encoding (expected UTF-8)` | Le fichier Markdown n'est pas encodé en UTF-8. |
| `JSON must be an array of objects for CSV conversion` | L'entrée json-to-csv n'est pas un tableau. |
| `JSON array is empty` | L'entrée json-to-csv est un tableau vide. |
| `CSV file is empty or has no valid rows` | Le fichier CSV n'a aucune ligne de données. |
| `XML structure cannot be converted to CSV` | Le XML n'est pas tabulaire (xml-to-csv). |
| `Turnstile verification failed` | Le défi anti-bot Cloudflare Turnstile a échoué. |
| `Turnstile token required` | Requête de widget sans jeton Turnstile. |

---

## 401 Unauthorized

Renvoyé lorsque l'authentification est manquante ou invalide.

| Message | Condition |
|---------|-----------|
| `Authentication required` | Aucune clé API ni jeton JWT fourni dans la requête. |
| `Invalid API Key format` | La clé API est trop courte ou ne commence pas par `sk_` ou `pk_`. |
| `Invalid API Key` | Le hachage de la clé API est introuvable dans la base de données. |
| `API Key revoked` | La clé API a été désactivée depuis le tableau de bord. |
| `Token has expired` | Le jeton d'accès JWT a expiré (durée de vie d'1 heure). |
| `Invalid token` | Le JWT est mal formé, altéré ou invalide d'une autre manière. |
| `Refresh token has expired` | Le jeton de rafraîchissement a expiré (durée de vie de 7 jours). |
| `Invalid refresh token` | Le jeton de rafraîchissement est mal formé ou invalide. |
| `Invalid token type` | Le jeton a été décodé avec succès mais n'est pas du type attendu (refresh). |
| `No refresh token` | L'endpoint de rafraîchissement du widget a été appelé sans cookie refresh_token. |
| `Refresh token not found` | Le jeton de rafraîchissement présenté n'est stocké pour aucune session. |
| `User not found or invalid` | Le jeton a été décodé mais son sujet ne correspond plus à un compte utilisable. |
| `Project not found` | L'identifiant de projet porté par la clé ou le jeton n'a pas pu être analysé lors de la vérification du quota d'ops. |

---

## 402 Payment Required

Renvoyé lorsqu'une limite d'utilisation est dépassée. La répartition entre `402` et `403` n'est pas symétrique, et elle prend souvent les développeurs de court. **Toute condition de quota répond `402`**, tout comme un endpoint V2 désactivé sur votre plan. **Les restrictions de fonctionnalités V1 répondent `403`** (async, webhooks, sortie ZIP, authentification de base, lot). La limitation de débit est un mécanisme distinct qui répond [`429`](#429-too-many-requests), jamais `402`.

| Message | Condition |
|---------|-----------|
| `Monthly operations limit reached ({used}/{limit}). Upgrade your plan to continue.` | Le compteur mensuel unifié d'ops a atteint l'allocation du plan. Plan Founding : 500 ops. Chaque endpoint puise dans ce compteur unique. Sur n'importe quel plan payant avec dépassement activé, les requêtes continuent à $0.02/op au lieu d'échouer. |
| `This request needs {units} operations but only {remaining} of your {limit} monthly operations remain. Upgrade your plan to continue.` | La requête en lot dépasserait le quota mensuel d'ops restant. L'intégralité du lot est rejetée d'emblée. |
| `No active billing period found for this project. Contact support to restore your subscription.` | Le projet n'a aucune période d'utilisation et aucune n'a pu être provisionnée depuis son abonnement. Le contrôle échoue en mode fermé plutôt que d'accorder une opération gratuite. |
| `Storage limit reached. Delete files or upgrade your storage plan to continue.` | L'utilisation du stockage du projet a atteint l'allocation de stockage du plan. |
| `Active watcher limit reached ({active_count}/{limit}). Delete an existing watcher or upgrade your plan to add more.` | Le projet détient déjà le nombre maximal de watchers actifs de son plan. Les watchers ne consomment pas d'ops ; il s'agit d'un plafond sur le nombre existant simultanément. |
| `{Feature} is not available on your current plan. Upgrade to a V2-inclusive plan to access this endpoint.` | Un endpoint V2 est désactivé pour le plan. |

L'épuisement des crédits IA mensuels ne produit pas de `402`. L'extraction de schéma se rabat sur le résultat heuristique et CSS, et la requête aboutit quand même. Les allocations, les tarifs et ce qui compte pour une opération figurent dans [Limites de débit et quotas](/fr/docs/reference/rate-limits.md).

---

## 403 Forbidden

Renvoyé lorsque l'accès est refusé en raison de restrictions liées au type de clé, au domaine, à une fonctionnalité de plan V1 ou à l'endpoint. Les restrictions d'endpoints V2 font exception : elles répondent [`402`](#402-payment-required), pas `403`.

### Restrictions de clé API et de jeton

| Message | Condition |
|---------|-----------|
| `Private API keys cannot be used from browsers` | Une clé privée (`sk_...`) a été utilisée dans une requête comportant un en-tête `Origin` de navigateur. Utilisez plutôt une clé publique avec JWT. |
| `Domain {origin} not authorized` | L'origine de la requête ne correspond à aucun domaine de la liste des domaines autorisés de la clé API. |
| `Public API keys can only be used to generate JWT tokens or fetch widget branding. Please exchange your public key for a JWT token at /v1/auth/token, then use the token for API calls.` | Une clé publique a été utilisée sur un chemin autre que `/auth/token` ou `/auth/branding`. Échangez-la d'abord contre un JWT. |
| `Endpoint '{path}' not allowed for this API key` | La liste `allowed_endpoints` de la clé API n'inclut pas le chemin demandé. |
| `Endpoint '{path}' not allowed for this token` | La liste `allowed_endpoints` du jeton JWT n'inclut pas le chemin demandé. |
| `Token issued for different origin` | L'origine de la requête ne correspond pas à l'origine enregistrée dans le JWT (empêche le vol de jeton). |
| `Parent origin does not match token` | L'en-tête `X-Parent-Origin` ne correspond pas à ce qui a été validé lors de l'émission du jeton. |

### Restrictions de fonctionnalités du plan

| Message | Condition |
|---------|-----------|
| `Async processing is not available on your current plan. Please upgrade to access this feature.` | `async_mode=true` sur un plan sans accès asynchrone. |
| `Webhook callbacks is not available on your current plan. Please upgrade to access this feature.` | `callback_url` fourni sur un plan sans accès aux webhooks. |
| `ZIP output bundling is not available on your current plan. Please upgrade to access this feature.` | `output_format=true` sur un plan sans accès à la sortie ZIP. |
| `Basic authentication, cookies & custom headers is not available on your current plan. Please upgrade to access this feature.` | `auth`, `cookies` ou `headers` utilisés sur un plan sans accès à l'authentification de base. |
| `Batch processing is not available on your current plan. Please upgrade to access this feature.` | Plusieurs URL soumises sur un plan avec un batch_limit de 0. |
| `Batch size {N} exceeds your plan's limit of {M} URLs per batch.` | Le nombre d'URL dépasse la limite de taille de lot du plan. |
| `Website crawling is not available on your current plan. Please upgrade to access this feature.` | Endpoint de capture de site web utilisé sur un plan avec un crawl_mode "none" (plan Founding). |
| `Full website crawling requires a Studio plan or higher. Your plan supports sitemap-based crawling only.` | `crawl_mode=full` demandé sur un plan Indie qui ne prend en charge que le crawl basé sur le sitemap. |

### Restrictions de widget

| Message | Condition |
|---------|-----------|
| `Widget API key has been revoked` | La clé API interne liée au widget a été désactivée. |
| `Domain {origin} is not authorized for this widget` | Le domaine d'intégration du widget ne figure pas dans la liste des domaines autorisés du widget. |
| `Refresh token does not match widget` | L'ID de projet du jeton de rafraîchissement ne correspond pas au projet du widget. |
| `Batch status requires a private API key` | Une clé publique ou de tableau de bord a tenté d'accéder à `GET /v1/convert/batch/{batch_id}`. |
| `Access denied` | Tentative d'accès à une ressource (statut de tâche, fichier) appartenant à un autre projet. |

Deux autres messages `403` ne concernent ni les clés ni les plans : `Account suspended`, renvoyé pour chaque requête dès que le compte derrière la clé ou le jeton est suspendu, et `robots.txt disallows fetching this URL (request sent respect_robots=true).`, renvoyé par perceive lorsque vous avez demandé le respect de robots et que la cible interdit ce chemin.

---

## 404 Not Found

| Message | Condition |
|---------|-----------|
| `Job not found` | ID de tâche de conversion introuvable dans la base de données (interrogation du statut). |
| `Batch not found` | L'ID de lot n'a aucune ligne d'activité correspondante pour ce projet. |
| `File not found` | Le fichier demandé n'existe pas dans le stockage (endpoint de téléchargement). |
| `Widget not found` | ID de widget introuvable ou widget désactivé. |
| `Operation not found`, `Ingest job not found`, `Watcher not found` | Un identifiant de ressource V2 qui n'existe pas, ou qui appartient à un autre projet. L'existence n'est jamais divulguée entre projets. |
| `Not Found` | Le chemin n'est pas une route de l'API. Vérifiez le chemin et le préfixe de version. |

---

## 409 Conflict

| Message | Condition |
|---------|-----------|
| `job_id already in use` | Un `job_id` fourni par le client est déjà revendiqué par un autre projet. Choisissez un autre identifiant, ou laissez l'API en générer un. |
| `A completion webhook is only delivered for completed jobs.` | Une nouvelle tentative de webhook a été demandée pour une tâche d'ingestion qui n'a pas atteint l'état `completed`. |

---

## 410 Gone

L'artefact a existé, mais il a dépassé la fenêtre de rétention de fichiers de votre plan et n'est plus dans le stockage. La rétention dépend du plan ; voir [Limites de débit et quotas](/fr/docs/reference/rate-limits.md).

| Message | Condition |
|---------|-----------|
| `The artifact is no longer in storage (it may have passed your plan's file-retention window). Re-run the perceive request to regenerate it.` | Téléchargement d'artefact sur `GET /v2/perceive/{operation_id}`. |
| `The batch archive is no longer in storage (it may have passed your plan's file-retention window).` | Téléchargement ZIP sur `GET /v2/perceive/batch/{job_id}`. |

Considérez `410` comme définitif pour cet objet. Relancer la requête produit un nouvel artefact ; réessayer le téléchargement n'y changera rien.

---

## 413 Payload Too Large

Renvoyé lorsque le fichier téléversé dépasse la taille de fichier maximale du plan.

<div class="alert alert-warning">
<strong>Corps imbriqué :</strong> l'objet structuré est la valeur de <code>detail</code>, pas un objet de premier niveau. Lisez <code>body.detail.max_size</code>.
</div>

```json
{
    "detail": {
        "error": "File too large",
        "file_size": 10485760,
        "max_size": 5242880,
        "tier": "free",
        "key_type": "private"
    }
}
```

| Champ | Description |
|-------|-------------|
| `error` | Toujours `"File too large"`. |
| `file_size` | La taille du fichier téléversé en octets. |
| `max_size` | La taille de fichier maximale autorisée pour votre plan, en octets. |
| `tier` | Le slug de votre plan d'abonnement (par ex. `"free"`, `"starter"`, `"pro"`), avec repli sur `"free"` lorsqu'aucun plan n'est résolu. Les slugs sont des identifiants API stables ; les noms commerciaux sont Founding (`free`), Indie (`starter`), Studio (`pro`) et Production (`business`). |
| `key_type` | Le type de clé API utilisé : `"private"`, `"public"` ou `"unknown"`. |

La limite est vérifiée sur le nombre exact d'octets de la partie téléversée, avant tout début de travail de conversion. Un fichier dont la taille vaut exactement `max_size` est accepté ; seul un fichier plus grand est rejeté. L'en-tête `Content-Length` sert de repli pour les anciens points d'appel qui ne transmettent pas leur objet d'upload à la vérification.

`POST /v2/ingest/files` n'utilise pas cette forme. Il répond `413` avec un `detail` sous forme de simple chaîne : `File '{filename}' exceeds the {max_size}-byte limit.`

Les plafonds par plan sont listés dans [Limites de débit et quotas](/fr/docs/reference/rate-limits.md), et les chemins d'upload concernés dans [Ingestion de fichiers](/fr/docs/guides/file-ingestion.md).

---

## Erreurs de conversion navigateur (415 / 422 / 502 / 504) {: #browser-conversion-errors-415-422-502-504 }

Les conversions d'URL distinguent une défaillance du **site cible ou de l'entrée** (un `4xx`, `502` ou `504` sur lequel vous pouvez agir) d'une défaillance de **notre moteur** (un `500`). Les trois endpoints de conversion d'URL V1 (`url-to-pdf`, `url-to-screenshot`, `url-to-markdown`) renvoient ces échecs typés avec un `code` lisible par une machine à côté de `detail` :

```json
{
    "error": "Gateway Timeout",
    "code": "upstream_timeout",
    "detail": "The target site took too long to respond while rendering PDF for https://example.com."
}
```

| Code | champ `code` | Condition |
|------|--------------|-----------|
| `415` | `unsupported_content_type` | La cible a renvoyé un contenu que le convertisseur ne peut pas rendre, par exemple `application/json` envoyé vers `url-to-pdf` ou `url-to-screenshot`. Utilisez `url-to-markdown` pour le JSON. |
| `422` | `selector_not_found` | Un `wait_for_selector` fourni par l'appelant n'est jamais apparu dans le délai `wait_for_selector_timeout`. |
| `502` | `upstream_unreachable` | Le site cible n'a pas pu être atteint (échec DNS ou de connexion). |
| `502` | `empty_render` | La navigation s'est terminée mais la page n'a produit aucun contenu capturable. |
| `504` | `upstream_timeout` | Le site cible a mis trop de temps à répondre ou à terminer son chargement. |

Ces cinq valeurs constituent tout le vocabulaire. Aucune autre famille d'endpoints n'émet de `code`, V2 comprise : un échec V2 revient sous forme de simple chaîne `detail`. La classe de base de l'enveloppe définit un sixième slug, `conversion_error`, mais rien ne le déclenche, donc il ne vous parvient jamais. Branchez votre code sur les cinq valeurs ci-dessus et traitez toute autre valeur comme inconnue.

<div class="alert alert-info">
Un `500` signifie désormais que notre moteur a rencontré une défaillance, si bien que réessayer une requête identique a peu de chances d'aider. Un `502`/`504` signifie que la <em>cible</em> a mal fonctionné : réessayez, ou vérifiez l'URL.
</div>

Un `504` peut aussi arriver sous deux formes non typées : `{"error": "Request timeout"}` lorsque la requête dépasse le budget de 300 secondes de la passerelle, et un `detail` sous forme de simple chaîne portant le message de délai lorsqu'une conversion de document (LibreOffice) expire. Ni l'une ni l'autre ne porte de `code`.

---

## 422 Unprocessable Entity

Les échecs de validation de schéma renvoient deux tableaux parallèles. `detail` est la sortie brute du validateur, c'est ce que vous remappez sur vos champs de formulaire. `errors` contient une chaîne lisible par un humain par problème, c'est ce que vous affichez à l'utilisateur.

```json
{
    "detail": [
        {
            "loc": ["body", "max_pages"],
            "msg": "Input should be a valid integer, unable to parse string as an integer",
            "type": "int_parsing"
        }
    ],
    "errors": [
        "body.max_pages: Input should be a valid integer, unable to parse string as an integer (you sent 'ten')"
    ]
}
```

Trois valeurs de `type` méritent d'être traitées nommément :

| `type` | Signification |
|--------|---------|
| `extra_forbidden` | Champ inconnu. Les schémas de requête V2 rejettent les clés inconnues au lieu de les ignorer : un paramètre mal orthographié devient un `422` qui nomme le champ, plutôt qu'une option silencieusement abandonnée. |
| `missing` | Un champ obligatoire n'a pas été envoyé. |
| `json_invalid` | Le corps de la requête n'était pas du JSON valide. |

<div class="alert alert-warning">
<strong>Une exception :</strong> la validation par élément sur <code>POST /v2/perceive/batch</code> renvoie un <code>422</code> dont le <code>detail</code> est une simple liste d'objets <code>{"loc", "msg"}</code>, sans clé <code>type</code> ni tableau <code>errors</code> de premier niveau. Les analyseurs qui supposent que <code>errors</code> est toujours présent échoueront à cet endroit.
</div>

Un `422` accompagné du code `selector_not_found` est autre chose : une précondition de rendu qui a échoué, traitée dans [Erreurs de conversion navigateur](#browser-conversion-errors-415-422-502-504).

---

## 429 Too Many Requests

La limitation de débit est un contrôle d'équité sur une courte fenêtre, distinct du quota mensuel d'ops. L'épuisement du quota répond [`402`](#402-payment-required) ; seul le limiteur de débit répond `429`.

| Message | Condition |
|---------|-----------|
| `Rate limit exceeded. Please slow down and retry shortly.` | Une fenêtre de débit de requêtes du projet a été dépassée. Les compteurs sont par projet et cloisonnés par type de clé : le trafic public et le trafic privé n'en partagent donc pas un seul. |

Un `429` porte quatre en-têtes :

| En-tête | Signification |
|--------|---------|
| `RateLimit-Limit` | Nombre de requêtes autorisées dans la fenêtre déclenchée. |
| `RateLimit-Remaining` | Requêtes restantes dans cette fenêtre, `0` en cas de rejet. |
| `RateLimit-Reset` | Nombre de secondes avant la réinitialisation de la fenêtre. |
| `Retry-After` | La même valeur que `RateLimit-Reset`. Attendez ce délai avant de réessayer. |

Ces en-têtes n'apparaissent que sur le `429`. Les réponses réussies ne portent aucun en-tête de limite de débit ni d'ops restantes : vous ne pouvez donc pas lire votre budget restant sur une réponse, consultez votre utilisation dans le tableau de bord. Voir [Limites de débit et quotas](/fr/docs/reference/rate-limits.md).

---

## 500 Internal Server Error

| Message | Condition |
|---------|-----------|
| `Conversion failed: {error}` | Une erreur inattendue pendant une conversion de **fichier téléversé**. Les conversions d'URL n'utilisent pas ce message : elles remontent via l'enveloppe typée ci-dessus, ou via le corps générique ci-dessous. |
| `Perception failed. Reference operation_id '{id}' when contacting support.` | Une défaillance inattendue au cours d'une exécution `/v2/perceive`. Les autres endpoints V2 ont leurs équivalents, comme `Distillation failed. Reference operation_id ...` et `Could not start ingest job. Reference job_id ...`. Citez l'identifiant lorsque vous contactez le support. |

Toute défaillance survenant en dehors d'un convertisseur ne vous parvient jamais sous forme de texte. Elle revient sans aucun `detail` :

```json
{
    "error": "Internal server error",
    "event_id": "a1b2c3d4"
}
```

Un cas surprend souvent : un corps JSON mal formé envoyé à un endpoint d'URL V1 (`url-to-pdf`, `url-to-screenshot`, `url-to-markdown`, `website-to-pdf`, `website-to-screenshot`) renvoie ce `500` plutôt qu'un `422`, car ces endpoints lisent le corps brut. Le même corps mal formé sur un endpoint V2 renvoie un `422` avec `type: json_invalid`.

Si vous rencontrez des erreurs 500 persistantes, le problème vient probablement du fichier ou de l'URL d'entrée. Essayez avec une entrée différente pour isoler le problème.

---

## 503 Service Unavailable

| Message | Condition |
|---------|-----------|
| `Converter not available: {endpoint}` | Le convertisseur demandé n'est pas enregistré ou n'est pas en cours d'exécution. |
| `Converter not available` | Le convertisseur basé sur l'URL pour l'endpoint demandé n'est pas disponible. |
| `The conversion service is at capacity. Please retry shortly.` | Le pool de rendu navigateur n'a plus de créneau libre. Envoyé avec `Retry-After: 30`. |
| `Server is at capacity. Please retry shortly.` | Le portillon d'admission des conversions CPU est plein : trop de conversions de fichiers, ou trop d'octets, déjà en cours. Envoyé avec `Retry-After: 10`. |
| `Search is temporarily unavailable. Please try again later.` | Le fournisseur de recherche en amont derrière lookup est injoignable ou mal configuré. |
| `Turnstile verification unavailable` | Le service de vérification Cloudflare Turnstile est injoignable. |

Deux portillons de capacité distincts existent et ils demandent des attentes différentes : lisez `Retry-After` plutôt que d'en supposer une. Ces erreurs sont par ailleurs transitoires, réessayez après un court délai.

---

## Erreurs des endpoints V2

Les [endpoints d'intelligence web V2](/fr/docs/concepts/v1-and-v2.md) réutilisent les codes de statut ci-dessus, avec quelques conditions spécifiques à la V2 qui méritent d'être signalées.

### Quota et plan (402 / 403)

Les opérations V2 sont décomptées sur le même quota mensuel unifié d'ops que les conversions V1 : une op par unité de travail. N'importe quel plan payant avec dépassement activé ($0.02/op) permet d'aller au-delà de l'allocation ; sinon, la limite est stricte.

| Code | Condition |
|------|-----------|
| `402` | Le quota mensuel d'ops est épuisé. Tous les endpoints V2 ([perceive](/fr/docs/endpoints/perceive.md), [discover](/fr/docs/coming-soon/discover.md), [lookup](/fr/docs/coming-soon/lookup.md), [distill](/fr/docs/coming-soon/distill.md), [ingest](/fr/docs/endpoints/ingest.md)) facturent ce compteur unique aux côtés des conversions V1. |
| `402` | La limite de watchers actifs (`max_watchers`) est atteinte ([watch](/fr/docs/coming-soon/watch.md)). Les watchers sont un plafond séparé et ne consomment jamais d'ops. |
| `402` | L'endpoint est désactivé pour le plan. Les restrictions V2 répondent `402`, contrairement aux restrictions de fonctionnalités V1, qui répondent `403`. |
| `403` | L'endpoint ne figure pas dans la liste d'autorisation `allowed_endpoints` de la clé API. |

Deux comportements V2 ne sont délibérément pas des erreurs. L'épuisement du solde de crédits IA ne fait pas échouer la requête : l'extraction de schéma se rabat sur le résultat heuristique et CSS. Et une exécution [distill](/fr/docs/coming-soon/distill.md) multi-URL qui franchit la limite d'ops en cours de route ne renvoie pas non plus de `402` : elle s'arrête là, renvoie les URL qu'elle a terminées et ajoute un avertissement indiquant combien ont été ignorées.

### Validation (422)

Chaque schéma de requête V2 rejette les clés inconnues : un paramètre mal orthographié devient un `422` qui nomme le champ. La forme du corps est décrite dans [422 Unprocessable Entity](#422-unprocessable-entity).

| Endpoint | Condition |
|----------|-----------|
| [perceive](/fr/docs/endpoints/perceive.md) | `proxy_url`, `geolocation` ou `action_chain` a été envoyé ; ces champs sont réservés pour une version ultérieure. |
| [distill](/fr/docs/coming-soon/distill.md) | Ni `schema` ni `prompt` n'a été fourni (envoyer les deux est acceptable, `schema` l'emporte) ; aucun ou les deux de `urls` et `discover_from` fournis ; un champ CSS invalide, un type de champ non pris en charge ou une regex présentant un risque de backtracking catastrophique. |
| [watch](/fr/docs/coming-soon/watch.md) | `frequency_minutes` en dessous du plancher horaire de 60 minutes ; un corps `PATCH` vide. |
| [ingest](/fr/docs/endpoints/ingest.md) | Le `mode` ne correspond pas à la source (mode `urls` sans `urls`, ou `sitemap`/`crawl` sans `url` de départ). |

### Fournisseur de recherche (502 / 503)

L'endpoint [lookup](/fr/docs/coming-soon/lookup.md) dépend d'un fournisseur de recherche en amont. Le texte d'erreur brut du fournisseur n'atteint jamais le client.

| Code | Message | Condition |
|------|---------|-----------|
| `502` | `The search provider returned an error. Please try again.` | Le fournisseur a renvoyé une réponse d'erreur ou une panne de transport non réessayable. |
| `503` | `Search is temporarily unavailable. Please try again later.` | Le fournisseur est mal configuré (clé manquante) ou temporairement injoignable. Réessayez plus tard. |

### Non trouvé (404)

`GET` et `DELETE` sur un `operation_id`, `job_id` ou `watcher_id` V2 qui n'existe pas, ou qui appartient à un autre projet, renvoient `404`. L'existence n'est jamais divulguée entre projets.

### Accepté (202)

[Ingest](/fr/docs/endpoints/ingest.md) est toujours asynchrone : `POST /v2/ingest` répond `202` avec un `job_id` que vous interrogez. Les lots perceive de plus de 10 URL répondent `202` avec le statut `queued`. Un lot de 10 URL ou moins répond normalement en ligne, mais s'il dépasse la fenêtre en ligne il bascule en `202` avec le statut `processing` et un avertissement : gérez donc `202` quelle que soit la taille du lot.

Un `202` signifie aussi que les échecs ultérieurs ne sont pas des erreurs HTTP. Interrogez la tâche et lisez sa charge utile de statut, comme décrit dans [Tâches synchrones et asynchrones](/fr/docs/concepts/sync-and-async.md).

---

## Dépannage

### Problèmes d'authentification

- **Vous obtenez un 401 ?** Vérifiez que votre clé API est valide et active dans le tableau de bord. Si vous utilisez un JWT, assurez-vous que le jeton n'a pas expiré (durée de vie d'1 heure).
- **Vous obtenez un 403 concernant l'utilisation depuis un navigateur ?** Vous utilisez une clé privée (`sk_...`) depuis du code côté client. Passez à une clé publique avec JWT pour les requêtes basées sur un navigateur.
- **Vous obtenez un 403 concernant le domaine ?** Ajoutez votre domaine à la liste des domaines autorisés de la clé API dans le tableau de bord.

### Problèmes de conversion

- **Vous obtenez un 400 concernant le format de fichier ?** Assurez-vous que l'extension du fichier téléversé correspond à l'endpoint (par ex. `.json` pour json-to-xml, `.docx` pour doc-to-pdf).
- **Vous obtenez un 413 ?** Votre fichier dépasse la limite de taille du plan. Lisez `detail.max_size` dans la réponse, puis vérifiez la taille de fichier maximale de votre plan ou passez à un plan supérieur.
- **Vous obtenez un 402 ?** Vous avez atteint votre quota mensuel d'ops, le plafond de watchers ou la limite de stockage, ou bien le projet n'a aucune période de facturation active. Vérifiez votre utilisation dans le tableau de bord et consultez [Limites de débit et quotas](/fr/docs/reference/rate-limits.md).

### Problèmes d'accès aux fonctionnalités

- **Vous obtenez un 403 concernant les fonctionnalités du plan ?** La fonctionnalité V1 que vous essayez d'utiliser (async, lot, webhooks, sortie ZIP, authentification de base) nécessite un niveau de plan supérieur. Consultez le [tableau de restriction des fonctionnalités](/fr/docs/reference/rate-limits.md#403-une-fonctionnalite-de-lot-ou-v1-que-votre-plan-na-pas).
- **Vous obtenez un 402 sur un endpoint V2 qui n'a rien à voir avec le quota ?** Les restrictions d'endpoints V2 répondent `402`, pas `403`. Le message est `... is not available on your current plan. Upgrade to a V2-inclusive plan to access this endpoint.`

## Questions fréquentes

### Pourquoi l'API renvoie-t-elle 402 Payment Required pour une conversion de fichier ?

Une `402` signifie qu'une limite d'utilisation est épuisée : votre quota mensuel unifié d'ops (500 ops sur le plan Founding), le plafond de watchers actifs, ou l'allocation de stockage de votre projet. Elle couvre aussi deux cas hors quota : un projet sans période de facturation active, et un endpoint V2 désactivé sur votre plan. Les requêtes en lot qui dépasseraient le quota mensuel d'ops restant sont rejetées d'emblée avec une `402` pour le lot entier. Les conversions V1 et les opérations V2 puisent dans le même quota ; n'importe quel plan payant avec dépassement activé ($0.02/op) permet d'aller au-delà. La limitation de débit est un mécanisme différent et répond `429`.

### Comment corriger une erreur 401 Unauthorized de l'API de conversion ?

Vérifiez que la clé API est présente, commence par `sk_` ou `pk_`, et est toujours active dans le tableau de bord, car les clés révoquées renvoient `API Key revoked`. Si vous vous authentifiez avec un JWT, notez que les jetons d'accès expirent après 1 heure (`Token has expired`) et les jetons de rafraîchissement après 7 jours.

### Pourquoi est-ce que j'obtiens 413 Payload Too Large lors de l'envoi d'un fichier ?

Le fichier téléversé dépasse la taille de fichier maximale de votre plan, mesurée sur le nombre exact d'octets de la partie téléversée avant tout début de travail de conversion. Un fichier exactement à la limite est accepté. Le corps du `413` imbrique un objet structuré sous `detail`, avec `file_size`, `max_size` (tous deux en octets), `tier` et `key_type` : lisez-le donc comme `detail.max_size` et non comme un champ de premier niveau.

### Puis-je utiliser une clé API privée depuis du JavaScript navigateur ?

Non. Une clé privée (`sk_...`) utilisée dans une requête comportant un en-tête `Origin` de navigateur renvoie `403 Private API keys cannot be used from browsers`. Échangez une clé publique contre un JWT sur `/v1/auth/token` et utilisez ce jeton pour les appels API depuis un navigateur.

### Une erreur 503 Service Unavailable de l'API est-elle permanente ?

Non, les erreurs `503` telles que `Converter not available: {endpoint}` ou `Turnstile verification unavailable` sont généralement transitoires. Réessayez la requête après un court délai.
