code lisible
par machine, pas le message humain (les messages peuvent changer ; les codes sont
stables).
L’enveloppe d’erreur
Les réponses réussies ne contiennent jamais d’objet
error. Branche d’abord sur
le statut HTTP, puis sur error.code.Codes d’erreur
invalid_api_key
HTTP 401. Le header Authorization est manquant ou malformé, ou la clé est
inconnue, expirée ou révoquée — ou le créateur de la clé n’est plus membre de
l’organisation. Les clés expirées et inconnues sont intentionnellement
indistinguables.
Authorization: Bearer samsa_sk_…. Si la clé
a été révoquée ou a expiré, crées-en une nouvelle.
Ne réessaie pas — le résultat ne changera pas.
missing_scope
HTTP 403. La clé est valide mais n’a pas le scope
que le endpoint requiert. param vaut "scope" et le message nomme le scope
manquant.
insufficient_credits
HTTP 402. Le pool de credits dépensable de l’organisation (abonnement plus
top-ups valides) est en dessous du coût de l’action. Voir Tarification.
Si l’organisation a au moins une équipe active, l’API renvoie à la place les codes
liés aux équipes insufficient_team_credits ou
insufficient_unallocated_credits — à deux
exceptions près. Une organisation système exemptée du budgeting par équipe reste sous
le régime org et garde ce code générique même avec des équipes actives. Et une
défaillance opérationnelle dans la déduction elle-même (par exemple une lecture de
solde dégradée) retombe sur ce code générique quel que soit le régime de budget. Un
simple insufficient_credits dans une configuration avec équipes peut donc être
transitoire ; réessaie avant de le traiter comme un solde épuisé.
GET /credits ultérieur
montre un available couvrant le coût, tu as peut-être rencontré le repli
opérationnel — réessaie une fois. Cette lecture n’est toutefois qu’un instantané : un
changement d’état depuis le refus (un top-up, un remboursement, un budget modifié ou
une réinitialisation de période) peut aussi l’expliquer, un vrai manque de solde n’est
donc pas exclu. Sinon, recharge ou fais évoluer le plan de l’organisation dans l’app,
puis réessaie. Rien n’a été facturé et aucun job n’a été créé.
subscription_inactive
HTTP 402. L’organisation n’a aucun abonnement utilisable (aucun actif, en
période de grâce, ou détenant des top-ups dépensables). Distinct de
insufficient_credits, où un abonnement existe mais le pool est trop bas.
insufficient_team_credits
HTTP 402. Les organisations peuvent répartir leurs credits d’abonnement mensuels
en budgets par équipe, et chaque clé API peut être assignée à une équipe. Une clé
assignée à une équipe avec budget dépense depuis le bucket mensuel de cette équipe ;
tout ce qui n’est alloué à aucun budget d’équipe forme le pool non alloué de
l’organisation. Les top-ups achetés sont exemptés des budgets et restent
disponibles pour couvrir le reste. Ce code survient quand le solde que la clé peut dépenser
— le plus petit du pool de plan restant de l’organisation et du budget restant de l’équipe,
plus les top-ups — est en dessous du coût de l’action. C’est exactement l’available
rapporté par GET /credits.
Le code nomme le régime de budget, pas le solde qui s’est épuisé. Une clé assignée à
une équipe budgétée reçoit ce code même quand c’est le pool de plan de l’organisation, et
non le budget d’équipe, qui était épuisé — l’équipe peut donc encore afficher de la marge.
Compare
plan_credits à scope.remaining sur
GET /credits pour voir lequel contraint.insufficient_unallocated_credits
HTTP 402. La clé n’est pas assignée à une équipe avec budget (non assignée, ou
son équipe n’a pas de budget), elle dépense donc depuis le pool non alloué de
l’organisation — les credits d’abonnement mensuels restants après soustraction des
budgets de toutes les équipes actives. Ce code survient quand le solde que la clé peut
dépenser — le plus petit du pool de plan restant de l’organisation et de la marge de ce pool
non alloué, plus les top-ups exemptés des budgets — est en dessous du coût de l’action, avec
la même réserve que ci-dessus sur le solde réellement épuisé. Les organisations sans équipe
active ne voient jamais les deux codes liés aux équipes — elles reçoivent le simple
insufficient_credits à la place.
not_found
HTTP 404. L’id est inconnu, ou il appartient à une autre organisation. Les deux
cas sont indistinguables par conception, donc l’existence n’est jamais divulguée
entre organisations.
validation_error
HTTP 422. Le corps de la requête ou un paramètre a échoué à la validation.
param nomme le champ fautif ; message explique la contrainte.
message et param, puis renvoie-la.
C’est une erreur client — réessayer la même requête échouera de façon identique.
svg_acceptance_required
HTTP 422. Seul POST /images/vectorizations
renvoie ce code. Le SVG est une exclusion de périmètre documentée au titre de
l’art. 50(2) du règlement européen sur l’IA : un SVG ne peut porter ni manifeste C2PA ni
filigrane intégré, donc les sorties vectorielles sont livrées non signées.
svg_acceptance doit donc être le booléen littéral true — une valeur absente, false
ou autre est rejetée avec ce code distinct, jamais avec le
validation_error générique. Cette reconnaissance est une
divulgation / preuve d’audit, pas une renonciation à la conformité. Rien n’est
facturé et aucun job n’est créé.
"svg_acceptance": true dès que ton
intégration expose l’avertissement de sortie non signée à celles et ceux qui exploitent
le résultat.
svg_phase1_scope_out_required
HTTP 403. Également réservé à la vectorisation, et distinct de
missing_scope — le scope de la clé est correct. La livraison exige en
plus une acceptation des ToS/AUP à jour, vérifiée côté serveur ; le flag
svg_acceptance de la requête n’est jamais retenu comme cette preuve. À la soumission,
une acceptation manquante ou périmée est refusée avant toute facturation. Le contrôle est
rejoué à la livraison : un job déjà accepté peut donc encore être refusé par
GET /images/vectorizations/{id} ou lors
de l’émission du webhook si l’acceptation expire entre-temps — un refus à la livraison
n’ajoute aucun frais, mais ne rembourse pas les crédits déjà consommés par le job terminé.
GET — le webhook n’est pas
rejoué automatiquement. Accepter est une divulgation, pas une renonciation.
rate_limited
HTTP 429. La clé a dépassé sa fenêtre de rate de requêtes par clé. La réponse
porte les headers Retry-After et X-RateLimit-*. Voir Rate limits.
Retry-After. Utilise un
backoff exponentiel pour des 429 répétés.
too_many_active_jobs
HTTP 429. L’organisation a atteint son plafond de jobs concurrents en cours. Le
message inclut le compte actuel et la limite, et un header Retry-After est défini.
GET ou utilise un webhook) avant
d’en soumettre d’autres, puis réessaie après Retry-After.
internal_error
HTTP 500. Une erreur inattendue du côté de Samsa. La réponse ne divulgue jamais
de détails internes — le request_id est ton point d’entrée pour le support.
request_id.
Utiliser request_id pour le support
Chaque erreur (et chaque succès) porte un request_id, aussi renvoyé dans le
header de réponse X-Request-ID. Il relie ton erreur côté client, le header de
réponse et les logs serveur de Samsa à une même requête. Log-le, et cite-le quand
tu contactes support@samsa.ai — c’est le moyen le plus
rapide pour nous de retrouver exactement ce qui s’est passé.

