Router

Router — Codes d'erreur

Tableau complet des codes 400/402/403/429/502 retournés par Router, leurs causes et les stratégies de retry recommandées.

Router — Codes d'erreur

Router suit les conventions HTTP standard. Le corps de réponse est toujours un objet JSON {"error": {"message": "...", "type": "..."}} — facile à parser.

Tableau des codes

Code Type Retry ?
200 Succès —
400 configuration_error Non
400 invalid_response_format / retention_not_supported Non
402 billing_error Non
403 permission_error Non
412 guard_halt Non
422 idempotency_conflict Non
422 schema_validation_failed Non (corrigez le prompt ou le schéma)
429 rate_limit_error Oui — backoff exponentiel
500 internal_error Oui (3 tentatives)
502 provider_error Oui après 30s

Détails par code ci-dessous.

400 — Configuration error

{
  "error": {
    "message": "Unknown model: anthropic/claude-foo",
    "type": "configuration_error"
  }
}

Causes fréquentes :

  • Identifiant de modèle inexistant (typo, modèle déprécié)
  • Body JSON invalide ou champ requis manquant (messages)
  • temperature hors plage [0, 2]
  • max_tokens au-delà du contexte du modèle

Action : corrigez la requête et renvoyez. Pas de retry automatique utile.

invalid_response_format (400, param: "response_format")

response_format n'a pas une forme utilisable : type inconnu, ou type: "json_schema" sans json_schema.name / json_schema.schema. Formes acceptées : {"type": "text"}, {"type": "json_object"}, {"type": "json_schema", "json_schema": {"name": ..., "schema": {...}}}.

retention_not_supported (400, param: "no_retention")

Vous avez demandé no_retention: true (ou X-No-Retention: true) sur un modèle qui ne peut pas garantir l'absence de rétention/entraînement. Router refuse plutôt que de servir une garantie affaiblie. Action : choisissez un autre modèle, ou retirez no_retention (voir Router — Confidentialité et rétention).

402 — Insufficient credits

{
  "error": {
    "message": "Insufficient credits",
    "type": "billing_error"
  }
}

Votre balance Router est en-dessous du coût estimé de la requête.

Action :

  1. Rechargez via /api/v1/billing/balance (ou l'interface)
  2. Activez l'auto-recharge pour éviter le problème (voir Router — Facturation)
  3. Côté code client : capture explicite + notification utilisateur (« Crédits IA épuisés, contactez votre administrateur »)

Pas de retry automatique — la requête échouera systématiquement tant que la balance n'est pas reconstituée.

403 — AI not enabled

{
  "error": {
    "message": "AI not enabled",
    "type": "permission_error"
  }
}

L'organisation propriétaire de cette clé API n'a pas le module IA activé.

Action : un administrateur doit activer le module dans Paramètres → Organisation → Modules → IA. Aucun retry n'aidera tant que ce n'est pas fait.

412 — Guard halt

{
  "error": {
    "message": "Guard blocked this request — 2 sensitive detection(s) in strict-mode policy",
    "type": "guard_halt",
    "audit_id": "halt-uuid-abc",
    "detections": [
      {"detector": "regex:nas", "category": "pii", "severity": "critical",
       "position_start": 23, "position_end": 34}
    ]
  }
}

Votre politique Guard est en mode strict et la requête contient au moins une détection critical ou high. L'appel upstream n'a pas eu lieu — aucun token consommé, aucun coût facturé.

Actions :

  1. Modifier le contenu de la requête pour retirer la donnée sensible
  2. OU désactiver le mode strict sur votre politique dans /settings/guard
  3. OU ajouter un allowlist pattern qui couvre le contenu légitime
  4. Utiliser l'audit_id pour inspecter la décision détaillée via Router — API d'audit

Le guard.halt webhook est déclenché en parallèle — voir Router — Webhooks pour brancher un alerting temps réel.

422 — Idempotency conflict

{
  "error": {
    "message": "Idempotency-Key already used with a different request body. Use a new key.",
    "type": "idempotency_conflict"
  }
}

Vous avez envoyé la même Idempotency-Key avec un body différent. Router refuse pour éviter qu'une réutilisation accidentelle de clé ne corrompe votre cache.

Actions :

  • Si c'est un vrai retry, envoyez le même body avec la même clé
  • Si c'est une nouvelle requête, générez une nouvelle clé (UUID recommandé)

422 — Schema validation failed

{
  "error": {
    "message": "Model output did not satisfy response_format.json_schema: $.ville: missing required properties: ville",
    "type": "invalid_request_error",
    "code": "schema_validation_failed",
    "param": "response_format",
    "raw_preview": "{\"nom\": \"Alice\"}..."
  }
}

Sur un modèle sans support natif de json_schema, la sortie ne respecte pas le schéma demandé (ou n'est pas du JSON). Router ne corrige jamais silencieusement : la réservation de crédits est libérée (aucune facturation pour cette sortie) et le brut est annexé en raw_preview (tronqué à 2000 caractères).

Actions :

  1. Renforcer le prompt (répéter les champs requis, exiger « JSON uniquement »)
  2. Simplifier le schéma (moins de champs requis, additionalProperties: false explicite)
  3. Basculer sur un modèle à support natif (GET /api/v1/models → supported_parameters)

429 — Rate limited

{
  "error": {
    "message": "Rate limited",
    "type": "rate_limit_error"
  }
}

L'organisation a dépassé sa limite de débit (par défaut : 60 requêtes/minute, ajustable par contrat).

Stratégie de retry recommandée : exponential backoff avec jitter.

import time
import random

def call_with_retry(client, **kwargs):
    delays = [1, 2, 4, 8, 16]  # secondes
    for attempt, base_delay in enumerate(delays):
        try:
            return client.chat.completions.create(**kwargs)
        except RateLimitError:
            if attempt == len(delays) - 1:
                raise
            jitter = random.uniform(0, base_delay * 0.5)
            time.sleep(base_delay + jitter)

À noter : Router lit l'en-tête Retry-After envoyé par les fournisseurs en amont. Quand disponible, il est forwardé dans la réponse — utilisez-le si présent au lieu d'un backoff fixe.

500 — Internal error

{
  "error": {
    "message": "...",
    "type": "internal_error"
  }
}

Erreur inattendue côté Router (DB, timeout, exception non gérée). Toujours loggé côté serveur.

Action : retry avec backoff (3 tentatives max). Si l'erreur persiste après les 3, signalez à [email protected] avec le request_id de la réponse (en-tête x-request-id).

502 — Provider error

{
  "error": {
    "message": "Provider error",
    "type": "provider_error",
    "details": "Anthropic returned 503 service_unavailable"
  }
}

Tous les fournisseurs IA compatibles avec votre requête ont échoué en cascade (Router a déjà essayé le fallback automatique). C'est rare — généralement causé par une panne globale d'Anthropic + OpenAI simultanément, ou par une panne réseau côté Meridian.

Action :

  1. Vérifier l'état des services
  2. Attendre 30 secondes puis retry une fois
  3. Si toujours 502 : signaler à support, utiliser un cache local en attendant

Bonnes pratiques générales

Idempotency

Pour les workloads où le même résultat doit être renvoyé sur un retry (ex. génération d'un document à partir d'un input fixe), envoyez un en-tête Idempotency-Key :

curl https://router.signalorange.ca/api/v1/chat/completions \
  -H "Authorization: Bearer $ROUTER_API_KEY" \
  -H "Idempotency-Key: req_2026-04-20-doc-123" \
  ...

⚠️ En cours d'implémentation — pour l'instant, gérez l'idempotence côté client avec un cache de réponses indexé sur le hash de la requête.

Logging des erreurs

Loggez systématiquement, pour chaque erreur :

  • Code HTTP
  • error.type et error.message
  • En-tête x-request-id (utile pour le support)
  • Timestamp local
  • Modèle demandé vs modèle réellement appelé (champ "model" de la dernière réponse réussie)

Circuit breaker

Pour les apps à haut volume, considérez un circuit breaker côté client : si Router renvoie 5 erreurs 5xx d'affilée, ouvrez le circuit pendant 60 secondes pour éviter d'aggraver la charge en aval.

Et après ?