Aller au contenu
Sandbox

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.

Voir en Markdown

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 sur https://platform.noryo.app ;
  • curl, ou un Node.js récent où fetch est 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.

Note

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_..."
Shell

Ne 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

POST/transactions/v1/enrichments

Enrichir une transaction.

Référence Transaction Intelligence

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 par external_transaction_id, account_hint ou counterparty_hint ont 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 EUR est refusé, 100.50 ISK aussi (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, et lifecycle.state restait unknown.

  • 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"}'
Shell
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({ 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);
TypeScript

Statut 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"
  }'
Shell
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", 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);
TypeScript

Ce 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.used et signals.ignored disent quels signaux reçus ont été transmis au moteur et lesquels personne n'a lus (ici payment.mcc et payment.card_present).
  • resolution.policy.outcome est le verdict (resolved). Lisez-le, pas les nombres de confidence : des constantes déclarées, pas des probabilités (voir Sémantique des scores).
  • counterparty peut valoir null : un inconnu honnête, pas une panne, et counterparty_note dit 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.

Note

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

GET/transactions/v1/enrichments/{enrichment_id}

Relire un enrichissement rendu.

Référence Transaction Intelligence

Paramè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"
Shell
const 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);
TypeScript

La réponse 200 est un StoredEnrichment :

ChampContenu
enrichment_id, created_atLes mêmes qu'à la création.
expires_atAprès cette date, la relecture rend 404 : un enrichissement est relisible 30 jours.
resolutionLa résolution rendue à la création.
relationships, seriesCe 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)

POST/transactions/v1/coverage-windows

Déclarer ce qui a été fourni pour un compte, sur une période

Référence Transaction Intelligence

Corps de la requêterequis

application/json · CoverageWindow

  • account_hintobjectrequis

    Le compte concerné. iban quand il en a un, sinon external_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

    complete signifie 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. partial et unknown n'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"
  }'
Shell
const 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()); // 200
TypeScript

Ré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…"
}
JSON

provenance 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 / min
par clé · capacité 120 · recharge 2/s
600 / min
par workspace · capacité 600 · recharge 10/s

« 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

Publié le 2026-10-09 10:17 UTC (version 3).

Cette page vous a-t-elle aidé ?