Aller au contenu
Sandbox

Erreurs

Comment lire une erreur application/problem+json de Transaction Intelligence, quoi faire pour chaque statut HTTP, et quelles requêtes rejouer.

Voir en Markdown

Toute erreur rendue par l'API Transaction Intelligence a la même forme : un corps application/problem+json (RFC 9457) dont le champ type identifie l'erreur de façon stable. Le catalogue complet, généré depuis le contrat, est en bas de cette page.

La forme d'une erreur

Une erreur est un objet Problem.

ChampPrésenceRôle
typetoujoursUne URI stable, de la forme https://noryo.app/problems/transactions/<code>. C'est sur elle qu'un client se branche.
titletoujoursUne phrase lisible. Elle peut être reformulée : ne la comparez pas.
statustoujoursLe statut HTTP, répété dans le corps.
detailselon l'erreurUne phrase de contexte, jamais la donnée refusée : la portée (clé ou workspace) sur un 429, le rappel du schéma fermé sur invalid_input. Absente sur certaines erreurs.
errorssur un 422Une liste d'objets { field, code } : le champ en cause, en chemin pointé (amount.currency, account_hint.iban), et le code de la règle.
request_idtoujoursL'identifiant de la requête, à citer au support.

Schéma Problem

  • typestring · urirequis
    https://noryo.app/problems/transactions/account_inactivehttps://noryo.app/problems/transactions/engine_unavailablehttps://noryo.app/problems/transactions/idempotency_conflicthttps://noryo.app/problems/transactions/internal_errorhttps://noryo.app/problems/transactions/invalid_api_keyhttps://noryo.app/problems/transactions/invalid_idempotency_keyhttps://noryo.app/problems/transactions/invalid_inputhttps://noryo.app/problems/transactions/invalid_requesthttps://noryo.app/problems/transactions/malformed_bodyhttps://noryo.app/problems/transactions/not_foundhttps://noryo.app/problems/transactions/rate_limitedhttps://noryo.app/problems/transactions/request_entity_too_largehttps://noryo.app/problems/transactions/unknown_currencyhttps://noryo.app/problems/transactions/unsupported_media_type
  • titlestringrequis
  • statusintegerrequis
  • detailstring
  • errorsarray<object>
  • fieldstringrequis
  • codestringrequis
  • request_idstring

    Présent sur toute erreur rendue par l'API : c'est ce que vous donnez au support. Il est aussi dans l'en-tête noryo-request-id des réponses de succès. Deux réponses viennent de l'hébergeur, avant l'API, et ne sont pas des Problem : un verbe non déclaré sur un chemin déclaré rend 405 avec un corps vide et sans en-tête Allow ; un chemin inconnu rend 404. Ni l'un ni l'autre ne porte request_id. Mesuré au bord public ; documenté plutôt que masqué.

Un exemple, pour une date sans décalage horaire :

{
  "type": "https://noryo.app/problems/transactions/invalid_input",
  "title": "Une contrainte du schema d'entree n'est pas respectee.",
  "status": 422,
  "detail": "Le schema est ferme : un champ inconnu est refuse plutot qu'ignore.",
  "errors": [
    { "field": "occurred_at", "code": "must_be_rfc3339_with_offset" }
  ],
  "request_id": "req_01J9…"
}
JSON
Note

detail est une phrase de contexte, jamais la donnée refusée : un descripteur refusé peut contenir le nom d'une personne, et un message d'erreur voyage dans des journaux. errors[].field dit où regarder, errors[].code dit quelle règle.

Lire une réponse

Testez d'abord le Content-Type, puis le type. Un statut seul ne suffit pas : deux réponses décrites plus bas viennent de l'hébergeur, sans corps Problem.

const transaction = {
  descriptor: "CARTE 09/09 MONOPRIX PARIS 15",
  direction: "outgoing",
  occurred_at: "2026-09-09T07:31:00+02:00",
  amount: { value: "49.90", currency: "EUR" },
  payment: { rail: "card" },
  external_transaction_id: "tx-2026-09-09-0042",
};

const response = await fetch("https://api.noryo.app/transactions/v1/enrichments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.NORYO_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "tx-2026-09-09-0042",
  },
  body: JSON.stringify(transaction),
});

const contentType = response.headers.get("content-type") ?? "";
if (contentType.startsWith("application/problem+json")) {
  const problem = await response.json();
  const code = problem.type.replace("https://noryo.app/problems/transactions/", "");
  switch (code) {
    case "rate_limited": {
      const seconds = Number(response.headers.get("retry-after"));
      // attendre `seconds`, puis rejouer la même requête, même Idempotency-Key
      break;
    }
    case "invalid_input":
    case "unknown_currency":
      // problem.errors[].field et problem.errors[].code : corriger avant de renvoyer
      break;
    case "idempotency_conflict":
      // même clé, requête différente : ne pas rejouer tel quel
      break;
    default:
      console.error(code, problem.request_id);
  }
}
TypeScript

Branchez-vous sur type (ou sur son dernier segment) et sur errors[].code, jamais sur title ni detail, qui sont des phrases.

Que faire, statut par statut

StatuttypeCauseCe qu'il faut faire
400malformed_bodyLe corps n'est pas un JSON valide.Corrigez la sérialisation. Ne rejouez pas tel quel.
400invalid_idempotency_keyL'en-tête Idempotency-Key est hors alphabet.Corrigez l'en-tête.
400invalid_requestLa requête est adressée à un hôte non déclaré dans le contrat.Appelez https://api.noryo.app.
401invalid_api_keyCredential absente, mal formée, d'une autre famille, inconnue ou révoquée. Un seul code pour tous ces cas.Vérifiez la clé et l'en-tête Authorization. Une clé révoquée ne revient pas : créez-en une nouvelle sur Platform. Voir Authentification.
403account_inactiveLa clé est reconnue, mais le workspace ou l'organisation n'est pas actif.Rien à corriger dans la requête. Citez le request_id au support.
404not_foundSur GET /transactions/v1/enrichments/{enrichment_id} : identifiant malformé, inconnu, expiré, ou d'un autre workspace. Les quatre cas rendent la même réponse.Vérifiez l'identifiant stocké. Un enrichissement est relisible 30 jours ; au-delà, enrichissez de nouveau la transaction. Voir Relire un enrichissement.
409idempotency_conflictCette Idempotency-Key a déjà servi pour une requête différente.Ne rejouez pas tel quel : voir ci-dessous.
413request_entity_too_largeLe corps dépasse la borne.Réduisez le corps. Un appel traite une transaction.
415unsupported_media_typeLe corps n'est pas déclaré application/json.Envoyez Content-Type: application/json.
422invalid_input, unknown_currencyLe schéma est fermé : champ inconnu ou contrainte violée.Corrigez ce que errors[] nomme. Ne rejouez pas tel quel.
429rate_limitedLa limite de débit opérationnelle est atteinte, pour cette clé ou pour le workspace.Attendez Retry-After, puis rejouez la même requête.
500internal_errorUne erreur interne, qui ne vient pas de votre requête.Rejouez plus tard, avec la même Idempotency-Key : si la première tentative avait abouti, sa réponse revient en 200 au lieu d'un doublon. Si l'erreur persiste, citez le request_id au support.

422 : corriger avant de renvoyer

Le champ en cause est dans errors[].field, la règle dans errors[].code. Corrigez, puis renvoyez ; une autre règle peut alors être signalée. Rejouer la même requête sans la changer rend le même 422.

Une devise hors de l'enum Money.currency est rendue sous le type unknown_currency ; toute autre contrainte sous invalid_input. La liste des codes possibles est celle du runtime, par construction : lisez la réponse 422 de chaque POST dans la référence, et les cas fréquents dans Enrichir une transaction et Couverture d'observation.

409 : même clé, autre requête

« Différente » se juge sur l'empreinte économique (fingerprint) de la transaction : deux corps qui ne diffèrent que par external_transaction_id, account_hint ou counterparty_hint ont la même empreinte et sont la même requête, rejouée en 200. Un 409 ne se rejoue donc pas tel quel : soit vous vouliez rejouer la requête d'origine, et il faut lui rendre son corps ; soit c'est une nouvelle transaction, et il lui faut une nouvelle clé. Une clé est retenue au moins 24 heures.

429 : attendre Retry-After

Le detail dit si la limite atteinte est celle de la clé ou celle du workspace. Rien n'a été calculé, rien n'a été enregistré, aucun événement n'a été émis. L'en-tête Retry-After donne, en secondes entières, le délai après lequel la requête peut passer ; rejouez alors la même requête, avec la même Idempotency-Key s'il y en avait une. Voir Limites de débit.

Ce qui compte dans le débit et l'usage

Les portes s'enchaînent dans un ordre fixe : hôte, Content-Type, taille du corps, credential, authentification et débit, JSON, Idempotency-Key, schéma, traitement. Un refus avant la credential (400 sur l'hôte, 415, 413) ou par elle (401, 403) ne touche ni vos limites de débit ni votre usage. Une fois la credential acceptée, un 400 sur le JSON ou sur l'Idempotency-Key, un 422, un 404 ou un 409 a consommé un jeton de débit et compte dans votre usage comme une requête. Les réponses 422, 404 et 409 portent les en-têtes noryo-ratelimit-limit et noryo-ratelimit-remaining, comme les succès. Voir Usage et clés d'API.

Deux réponses sans Problem

Deux réponses viennent de l'hébergeur, avant l'API, et ne sont pas des Problem : un verbe non déclaré sur un chemin déclaré rend 405 avec un corps vide et sans en-tête Allow ; un chemin inconnu rend 404. Ni l'une ni l'autre ne porte request_id. D'où le test du Content-Type avant de lire le corps : un 404 sans application/problem+json est une faute de chemin, pas un enrichissement introuvable.

Citer le request_id au support

Toute erreur rendue par l'API porte request_id dans son corps ; les succès le portent dans l'en-tête noryo-request-id. Conservez-le dans vos journaux, à côté de l'identifiant public de la clé (key_id), et donnez-le au support. Ne joignez jamais le secret de la clé.

Le catalogue

Le catalogue ci-dessous est généré depuis le contrat servi sur https://api.noryo.app/openapi.json : la liste des type est celle que le runtime peut produire. Chaque code a sa page sous /transaction-intelligence/errors/<code>, par exemple /transaction-intelligence/errors/rate_limited.

Code
StatutCodeTitre
400invalid_idempotency_keyL'en-tête Idempotency-Key doit faire 1 à 255 octets d'ASCII visible (0x21 à 0x7E).
400invalid_requestLa requête n'est pas servie sur cet hôte.
400malformed_bodyLe corps de la requête n'est pas un JSON valide.
401invalid_api_keyLa credential présentée n'est pas utilisable.
403account_inactiveLe workspace ou l'organisation n'est pas actif.
404not_foundAucun enrichissement lisible sous cet identifiant.
Inconnu, expiré ou hors de votre périmètre : ces cas ne sont pas distingués.
409idempotency_conflictCette Idempotency-Key a déjà été utilisée pour une requête différente.
413request_entity_too_largeLe corps de la requête dépasse la borne.
415unsupported_media_typeLe corps doit être du application/json.
422invalid_inputUne contrainte du schéma d'entrée n'est pas respectée.
Le schéma est fermé : un champ inconnu est refusé plutôt qu'ignoré.
422unknown_currencyLa devise n'est pas dans le référentiel.
429rate_limitedTrop de requêtes.
La limite de débit de cette clef est atteinte. Rejouez après le délai de Retry-After.
500internal_errorUne erreur interne est survenue.
503engine_unavailableLe moteur de résolution n'est pas joignable.

Toutes les erreurs

Code
StatutCodeTitre
400invalid_idempotency_keyL'en-tête Idempotency-Key doit faire 1 à 255 octets d'ASCII visible (0x21 à 0x7E).
400invalid_requestLa requête n'est pas servie sur cet hôte.
400malformed_bodyLe corps de la requête n'est pas un JSON valide.
401invalid_api_keyLa credential présentée n'est pas utilisable.
403account_inactiveLe workspace ou l'organisation n'est pas actif.
404not_foundAucun enrichissement lisible sous cet identifiant.
Inconnu, expiré ou hors de votre périmètre : ces cas ne sont pas distingués.
409idempotency_conflictCette Idempotency-Key a déjà été utilisée pour une requête différente.
413request_entity_too_largeLe corps de la requête dépasse la borne.
415unsupported_media_typeLe corps doit être du application/json.
422invalid_inputUne contrainte du schéma d'entrée n'est pas respectée.
Le schéma est fermé : un champ inconnu est refusé plutôt qu'ignoré.
422unknown_currencyLa devise n'est pas dans le référentiel.
429rate_limitedTrop de requêtes.
La limite de débit de cette clef est atteinte. Rejouez après le délai de Retry-After.
500internal_errorUne erreur interne est survenue.
503engine_unavailableLe moteur de résolution n'est pas joignable.
Cette page vous a-t-elle aidé ?