Quickstart
De l'identité Noryo au premier enrichissement relu : activer le sandbox, créer une clé, appeler l'API en curl et en TypeScript, puis lire l'usage sur Platform.
Ce guide mène d'une identité Noryo à un premier enrichissement relu, en sept étapes, entièrement dans le sandbox : aucune clé de production n'existe aujourd'hui, et rien n'est facturé. Il vous faut :
- une identité Noryo (
https://auth.noryo.app) et l'appartenance à un workspace surhttps://platform.noryo.app; curl, ou un Node.js récent oùfetchest natif, pour les exemples TypeScript.
1. Activer le sandbox sur Platform
Connectez-vous à https://platform.noryo.app avec votre identité Noryo et ouvrez la page Clés d'API (/keys).
Complétez d'abord le profil développeur du workspace : prénom, nom, courriel vérifié, téléphone professionnel, nom d'organisation, puis ce que vous construisez et votre contexte d'intégration. Un owner ou un admin le remplit une fois, pour tout le workspace. Tant qu'un élément manque, l'activation est refusée et aucune clé n'est émise ; Platform liste les éléments manquants. Détail sur Accès production.
Le profil complet, un owner ou un admin clique sur Activer le sandbox Transaction Intelligence. L'activation est un choix du workspace, journalisé : elle ne crée rien d'autre qu'un droit d'émettre des clés nry_txn_sbx_, sans intervention de Noryo.
Sur Platform, vous êtes membre d'un workspace, l'organisation cliente de l'API : ni un utilisateur de l'application Noryo, ni un compte bancaire, ni l'utilisateur final de votre produit.
2. Créer une clé et copier le secret
Nommez la clé (par exemple « intégration de test ») et cliquez sur Créer une clé Sandbox. Un owner, un admin ou un developer peut créer et révoquer des clés ; tout membre peut les lister. Le nombre de clés actives par workspace est plafonné (le plafond est lu du serveur ; les révoquées ne comptent pas).
Le credential a la forme nry_txn_sbx_<key_id>.<secret>. Le key_id est public (10 caractères) et reste visible dans la liste. Le secret n'est affiché qu'une fois, à la création : Noryo ne le conserve pas, seul un vérificateur HMAC est stocké. Copiez-le maintenant. Perdu, il n'existe pas de bouton de rotation : révoquez la clé et créez-en une nouvelle. Une clé révoquée rend 401 dès la requête suivante.
3. Ranger le secret dans une variable d'environnement
Le secret vit côté serveur, dans une variable d'environnement. Tous les exemples lisent NORYO_API_KEY.
export NORYO_API_KEY="nry_txn_sbx_..."ShellNe le placez jamais dans un bundle navigateur, un dépôt public, un binaire mobile ou du JavaScript côté client. Une clé nry_mer_* (Merchant API) est refusée sur Transaction Intelligence, et réciproquement. Détail sur Authentification.
4. Premier appel : enrichir une transaction
Paramètres
- Idempotency-Keystringheader
Facultatif. Avec la même clef et la même requête : rejeu (200), la réponse d'origine est rendue telle quelle, version de moteur comprise. Avec la même clef et une requête différente : 409. « REQUÊTE DIFFÉRENTE » se juge sur l'empreinte économique (
fingerprint), et sur elle seule : deux corps qui ne diffèrent que parexternal_transaction_id,account_hintoucounterparty_hintont la même empreinte et sont la même requête pour l'idempotence — le premier envoi est rejoué tel quel. Ne réutilisez pas une clef pour deux transactions que vous distinguez par ces seuls champs. Aucune version de moteur n'entre dans cette identité : un déploiement entre deux tentatives ne transforme pas un rejeu en opération neuve. La valeur brute n'est jamais rangée ni journalisée. La clef est retenue au moins 24 heures : elle expire 24 heures après le premier envoi et n'est effacée que par la purge quotidienne qui suit cette expiration. Entre 24 et 48 heures après le premier envoi, la même clef ouvre donc une opération neuve (201) — jamais avant 24 heures. De 1 à 255 octets d'ASCII visible (0x21 à 0x7E) ; une valeur vide ou hors alphabet est refusée 400.
Corps de la requêterequis
application/json · TransactionInput
- descriptorstring · length 1–512requis
- directionstringrequis
incomingoutgoing - occurred_atstring · date-time
RFC 3339 avec décalage. Jamais réduit à une date : minuit n'est pas le même jour partout.
- amountMoney
- valuestringrequis
Décimal en chaîne. Un flottant perdrait des centimes en silence. Le nombre de décimales admis dépend de la devise :
5.905 EURest refusé,100.50 ISKaussi (ISK et JPY n'en ont aucune, KWD en a trois).^-?(0|[1-9][0-9]{0,15})(\.[0-9]{1,4})?$ - currencystringrequis
Une devise hors de cette liste est refusée 422
unknown_currency.CADCHFDKKEURGBPISKJPYKWDNOKPLNSEKUSD - paymentobject
- railstring · length ≤ 128
- mccstring
^[0-9]{4}$ - card_presentboolean
- network_merchant_idstring · length ≤ 128
- raw_typestring · length ≤ 128
- location_hintobject
- countrystring
^[A-Z]{2}$ - citystring · length ≤ 128
- postal_codestring · length ≤ 128
- counterparty_hintobject
L'IBAN est réduit dès l'entrée à son pays et son code banque. Le numéro de compte n'entre pas dans le système, donc ne peut pas en sortir.
- ibanstring
^[A-Z]{2}[0-9]{2}[A-Z0-9]{10,30}$ - bicstring
^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$ - account_hintobject
Le compte sur lequel la transaction est passée — à distinguer de
counterparty_hint, qui désigne l'autre bout. Sans lui, une paire de virement ne peut pas être identifiée : le seul signal qui la désigne est « mon compte est ta contrepartie, et ta contrepartie est mon compte ». L'IBAN est réduit dès l'entrée, comme celui de la contrepartie : le numéro de compte n'entre pas dans le système.external_account_id: votre propre identifiant du compte, pour les cartes, portefeuilles et rails sans IBAN. Il sert à une chose : relier la transaction à une couverture déclarée sur le même compte. Il n'entre pas dans l'empreinte. Mesuré : il était accepté, puis jeté — la série n'avait aucune source, etlifecycle.staterestaitunknown. - ibanstring
^[A-Z]{2}[0-9]{2}[A-Z0-9]{10,30}$ - external_account_idstring · length 1–128
- external_transaction_idstring · length 1–128
Votre propre identifiant de transaction. Il n'entre pas dans l'empreinte : il dit comment vous nommez la transaction, pas ce qu'elle est. Il sert à empêcher deux envois d'une même transaction de se rapprocher l'un de l'autre.
Réponses
200Déjà enrichie : requête rejouée, rien n'a été créé. Location est posé ici aussi : le client apprend où vit la ressource qu'il n'a pas créée.Enrichment
201Enrichissement créé.Enrichment
400Corps illisible, ou Idempotency-Key hors alphabet.Problem
401Credential absente, mal formée, d'une autre famille, inconnue, révoquée, ou dont le préfixe déclaré ne correspond pas au préfixe enregistré. Un seul code pour tous ces cas : les distinguer serait un oracle.Problem
403Workspace ou organisation inactif.Problem
409Cette Idempotency-Key a déjà servi pour une requête différente — c'est-à-dire d'empreinte économique (fingerprint) différente. Même clef + même empreinte = rejeu (200), jamais 409.Problem
413Corps trop volumineux.Problem
415Le corps doit être du application/json.Problem
422Schéma fermé : champ inconnu, ou contrainte violée. errors[].code nomme la règle, et la liste est celle du runtime, par construction : body_not_object, unknown_field, required_non_empty_string, too_long, contains_control_character, must_be_incoming_or_outgoing, must_be_rfc3339_with_offset, must_be_object, must_be_string, must_be_boolean, must_be_four_digits, must_be_iso_3166_1_alpha_2_uppercase, malformed_iban, malformed_bic, must_be_non_empty_string, amount_not_object, amount_value_not_string, amount_value_malformed, amount_currency_not_string, amount_currency_malformed, amount_currency_unknown, amount_precision_exceeds_currency. must_be_rfc3339_with_offset couvre la forme et le calendrier : 2026-02-30T00:00:00Z, +16:00 ou 23:59:60 sont refusés. must_be_object refuse aussi un tableau, pour les quatre familles. contains_control_character : un caractère de contrôle (C0, DEL, C1) dans un texte, tabulation, saut de ligne et retour chariot exceptés. amount_currency_unknown est rendu sous le type unknown_currency, les autres sous invalid_input.Problem
429rate_limited : la limite opérationnelle de débit est atteinte, pour cette clef ou pour le workspace (le detail le dit). Rien n'a été calculé, rien n'a été rangé, aucun événement n'a été émis. Rejouez après Retry-After. Voir x-noryo-rate-limit.Problem
500Erreur interne.Problem
Le corps est un TransactionInput. Deux champs suffisent : descriptor, le libellé tel que la banque le rend, et direction, incoming ou outgoing. Le schéma est fermé : un champ inconnu est refusé 422, jamais ignoré. Le corps est du application/json, sinon 415.
Corps minimal
curl -sS -i https://api.noryo.app/transactions/v1/enrichments \
-H "Authorization: Bearer $NORYO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"descriptor":"CARTE 09/09 MONOPRIX PARIS","direction":"outgoing"}'Shellconst response = await fetch("https://api.noryo.app/transactions/v1/enrichments", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NORYO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ descriptor: "CARTE 09/09 MONOPRIX PARIS", direction: "outgoing" }),
});
console.log(response.status, response.headers.get("location")); // 201
const enrichment = await response.json();
console.log(enrichment.enrichment_id);TypeScriptStatut 201 : l'enrichissement est créé. L'en-tête Location dit où il vit, et le corps porte son enrichment_id, de la forme enr_<uuid>. Conservez-le pour l'étape 5.
Exemple complet
L'exemple achat_carte du contrat : occurred_at en RFC 3339 avec décalage, amount.value en chaîne, le compte dans account_hint (pour relier une couverture), votre identifiant dans external_transaction_id.
curl -sS https://api.noryo.app/transactions/v1/enrichments \
-H "Authorization: Bearer $NORYO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"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", "mcc": "5411", "card_present": true },
"location_hint": { "country": "FR" },
"account_hint": { "iban": "FR7630006000011234567890189" },
"external_transaction_id": "tx-2026-09-09-0042"
}'Shellconst 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", mcc: "5411", card_present: true },
location_hint: { country: "FR" },
account_hint: { iban: "FR7630006000011234567890189" },
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",
},
body: JSON.stringify(transaction),
});
const enrichment = await response.json();
console.log(enrichment.resolution.policy.outcome);TypeScriptCe que la réponse contient
Extrait de la réponse 201 du contrat pour cet exemple :
{
"enrichment_id": "enr_3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"created_at": "2026-09-09T05:31:02Z",
"contract_version": "ti_v1",
"canonical_version": "canon_ti_v1",
"fingerprint": "canon_ti_v1:c2a1…",
"signals": {
"used": ["descriptor", "direction", "payment.rail", "location_hint.country", "occurred_at", "amount", "account_hint.iban", "external_transaction_id"],
"ignored": ["payment.mcc", "payment.card_present"],
"conflicting": []
},
"resolution": {
"policy": { "outcome": "resolved", "archetype": "achat_carte", "missing": [], "policy_version": "ti_resolution_v1" },
"counterparty": { "entity_id": "b83d1756-258c-4541-adc7-54eec83dbd3c", "name": "Monoprix", "kind": "brand" },
"category": { "slug": "alimentation.supermarche", "label": "Supermarché", "source": "entity_graph" },
"transaction_type": { "canonical": "card_purchase", "evidence": "rail_rule", "contradiction": null }
},
"relationships": [],
"series": [],
"relationships_policy": {
"detector_version": "rel_v1",
"calibration": "non_calibree",
"unresolved": [{ "type": "series", "outcome": "single_occurrence", "occurrences": 1 }]
}
}JSONÀ lire d'abord :
signals.usedetsignals.ignoreddisent quels signaux reçus ont été transmis au moteur et lesquels personne n'a lus (icipayment.mccetpayment.card_present).resolution.policy.outcomeest le verdict (resolved). Lisez-le, pas les nombres deconfidence: des constantes déclarées, pas des probabilités (voir Sémantique des scores).counterpartypeut valoirnull: un inconnu honnête, pas une panne, etcounterparty_notedit pourquoi.
Une seule occurrence ne fait pas une série : relationships_policy.unresolved le dit (single_occurrence). Tous les champs sont sur Enrichir une transaction.
L'en-tête Idempotency-Key est facultatif. Même clé et même requête : l'API rejoue la réponse d'origine (200) au lieu de créer un second enrichissement.
5. Relire l'enrichissement
/transactions/v1/enrichments/{enrichment_id}Relire un enrichissement rendu.
Référence Transaction IntelligenceParamètres
- enrichment_idstringpathrequis
Réponses
200Trouvé.StoredEnrichment
400Hôte non déclaré. Jamais rendu pour la forme de l'identifiant.Problem
401Credential inutilisable.Problem
403Workspace ou organisation inactif.Problem
404Malformé, inconnu, expiré, ou appartenant à un autre workspace — les quatre cas rendent la même réponse. Rendre 400 sur un identifiant malformé dirait qu'un enr_<uuid> bien formé « à la bonne tête » : c'est un oracle de forme, et il est fermé.Problem
429rate_limited : la limite opérationnelle de débit est atteinte, pour cette clef ou pour le workspace (le detail le dit). Rien n'a été calculé, rien n'a été rangé, aucun événement n'a été émis. Rejouez après Retry-After. Voir x-noryo-rate-limit.Problem
500Erreur interne.Problem
Avec l'identifiant reçu à l'étape 4 :
curl -sS https://api.noryo.app/transactions/v1/enrichments/enr_3f2504e0-4f89-41d3-9a0c-0305e82c3301 \
-H "Authorization: Bearer $NORYO_API_KEY"Shellconst enrichmentId = "enr_3f2504e0-4f89-41d3-9a0c-0305e82c3301";
const response = await fetch(
`https://api.noryo.app/transactions/v1/enrichments/${enrichmentId}`,
{ headers: { Authorization: `Bearer ${process.env.NORYO_API_KEY}` } },
);
console.log(response.status); // 200
const stored = await response.json();
console.log(stored.expires_at);TypeScriptLa réponse 200 est un StoredEnrichment :
| Champ | Contenu |
|---|---|
enrichment_id, created_at | Les mêmes qu'à la création. |
expires_at | Après cette date, la relecture rend 404 : un enrichissement est relisible 30 jours. |
resolution | La résolution rendue à la création. |
relationships, series | Ce que le workspace a appris depuis : un remboursement arrivé après coup, une occurrence de plus dans une série. |
Un identifiant inconnu, malformé, expiré ou d'un autre workspace rend 404, la même réponse dans les quatre cas. Voir Relire un enrichissement.
6. Déclarer une couverture d'observation (facultatif)
/transactions/v1/coverage-windowsDéclarer ce qui a été fourni pour un compte, sur une période
Référence Transaction IntelligenceCorps de la requêterequis
application/json · CoverageWindow
- account_hintobjectrequis
Le compte concerné.
ibanquand il en a un, sinonexternal_account_id— votre propre identifiant, pour les cartes, portefeuilles et rails non-SEPA. Au moins l'un des deux. Aucun des deux n'est conservé : nous n'en gardons qu'une empreinte opaque, portée à votre workspace — la même que celle de l'enrichissement, sans quoi la couverture ne croiserait jamais la série qu'elle qualifie. - ibanstring
^[A-Z]{2}[0-9]{2}[A-Z0-9]{10,30}$ - external_account_idstring · length 1–128
- covered_fromstring · daterequis
- covered_tostring · daterequis
On ne certifie pas ce qui n'a pas encore eu lieu : une date future est refusée.
- completenessstring
completesignifie quelque chose de précis : vous affirmez que toutes les opérations de ce compte sur cet intervalle nous ont été fournies. C'est la seule valeur qui nous autorise à conclure d'une absence.partialetunknownn'y autorisent pas — dix appels en octobre peuvent être dix opérations sur cent.completepartialunknown
Réponses
200Couverture enregistrée, ou fusionnée avec une voisine.CoverageWindowResult
400Corps illisible, ou hôte non déclaré.Problem
401Clef absente, inconnue ou révoquée.Problem
403Organisation ou workspace inactif.Problem
413Corps trop volumineux.Problem
415Le corps doit être du application/json.Problem
422Schéma fermé : champ inconnu, compte absent ou malformé, jour inexistant, intervalle inversé ou dans le futur, complétude inconnue. errors[].code nomme la règle, et la liste est celle du runtime, par construction : must_be_object, unknown_field, required, malformed_iban, must_be_non_empty_string, too_long, contains_control_character, account_identity_required, must_be_iso_date, after_covered_to, unknown_completeness, interval_in_future. must_be_iso_date couvre la forme et le calendrier (2026-02-30 est refusé). interval_in_future : covered_to au-delà de demain, date du serveur en UTC. provenance n'est pas un champ d'entrée : le schéma fermé le refuse unknown_field, la base ne le voit jamais.Problem
429rate_limited : la limite opérationnelle de débit est atteinte, pour cette clef ou pour le workspace (le detail le dit). Rien n'a été calculé, rien n'a été rangé, aucun événement n'a été émis. Rejouez après Retry-After. Voir x-noryo-rate-limit.Problem
500Erreur interne.Problem
Une couverture est un fait de compte et de période, pas de transaction : vous déclarez si toutes les opérations d'un compte entre deux dates ont été fournies (completeness : complete, partial ou unknown). Seul complete autorise Noryo à conclure d'une absence. Sans elle, une échéance absente ne prouve rien et le cycle de vie d'une série reste unknown. Le compte est celui de l'enrichissement (account_hint).
curl -sS https://api.noryo.app/transactions/v1/coverage-windows \
-H "Authorization: Bearer $NORYO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account_hint": { "iban": "FR7630006000011234567890189" },
"covered_from": "2026-07-01",
"covered_to": "2026-07-31",
"completeness": "complete"
}'Shellconst response = await fetch("https://api.noryo.app/transactions/v1/coverage-windows", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NORYO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
account_hint: { iban: "FR7630006000011234567890189" },
covered_from: "2026-07-01",
covered_to: "2026-07-31",
completeness: "complete",
}),
});
console.log(response.status, await response.json()); // 200TypeScriptRéponse 200 du contrat :
{
"covered_from": "2026-07-01",
"covered_to": "2026-07-31",
"completeness": "complete",
"provenance": "caller_asserted",
"merged_intervals": 0,
"policy_version": "cov_v1",
"note": "",
"request_id": "req_01J9…"
}JSONprovenance vaut toujours caller_asserted : Noryo enregistre votre déclaration sans la vérifier. Les intervalles qui se touchent fusionnent (merged_intervals) ; covered_to ne peut pas être dans le futur. Voir Couverture d'observation.
7. Lire l'usage sur Platform
Sur https://platform.noryo.app/usage, section Transaction Intelligence, vous lisez sur 24 h, 7 jours ou 30 jours, par jour UTC, par famille d'appel et par clé : le total des requêtes authentifiées, les succès (2xx), les erreurs client (4xx hors 429), les requêtes limitées (429, par clé ou par workspace) et les erreurs serveur (5xx). Une clé révoquée garde son historique ; le registre est conservé 35 jours.
Ce n'est pas un grand livre de facturation : un 422 compte comme une requête, un 401 n'est jamais compté, rien n'est facturé. Un zéro se distingue de « non observé » (avant la mise en service du registre) et d'« indisponible ». Voir Usage et clés d'API.
En-têtes et débit
Une fois la credential acceptée, chaque réponse porte noryo-request-id, l'identifiant à citer au support (sur une erreur, il est aussi dans le corps, request_id), ainsi que noryo-ratelimit-limit et noryo-ratelimit-remaining. Sur 429, Retry-After donne les secondes à attendre. Le débit est une protection opérationnelle :
debit_ti_v1operational_burst_protection · token_bucket« 120 requetes / minute » est traduit en seau a jetons : capacite 120 (la rafale toleree) rechargee a 2 jetons / seconde (le regime durable). Une clef au repos peut envoyer 120 requetes d'un coup, puis 2 par seconde ; sur la premiere minute apres un repos, jusqu'a 240 passent. Le workspace, lui, borne la somme de toutes ses clefs a 600 / minute (capacite 600, 10 / s) : cent clefs ne font pas cent fois la limite.
Ce n'est pas : sandbox_lifetime_quota · production_usage_quota · billing_entitlement · commercial_promise
Et ensuite
- Authentification : familles de clés, révocation, où ranger le secret.
- Enrichir une transaction, Relire un enrichissement, Couverture d'observation.
- Séries récurrentes et Relations : ce que plusieurs transactions font apparaître.
- Erreurs, Limites de débit, Limites du sandbox.
- La référence et le catalogue d'erreurs, générés depuis
https://api.noryo.app/openapi.json.
Publié le 2026-10-09 10:17 UTC (version 3).