# 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 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](https://developer.noryo.app/fr-FR/transaction-intelligence/production-access).

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`.

```bash
export NORYO_API_KEY="nry_txn_sbx_..."
```

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](https://developer.noryo.app/fr-FR/transaction-intelligence/authentication).

## 4. Premier appel : enrichir une transaction

**`POST /transactions/v1/enrichments`** — Enrichir une transaction.

#### Paramètres

| Nom | Dans | Requis | Type | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | — | string | 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. Motif : `^[\x21-\x7E]{1,255}$`. |

#### Corps de la requête

`application/json` · requis · Schéma `TransactionInput`

| Champ | Type | Requis | Description |
| --- | --- | --- | --- |
| `descriptor` | string · length 1–512 | oui | — |
| `direction` | string | oui | Valeurs : `incoming`, `outgoing`. |
| `occurred_at` | string · date-time | — | RFC 3339 avec décalage. Jamais réduit à une date : minuit n'est pas le même jour partout. |
| `amount` | Money | — | — |
| `amount.value` | string | oui | 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). Motif : `^-?(0\|[1-9][0-9]{0,15})(\.[0-9]{1,4})?$`. |
| `amount.currency` | string | oui | Une devise hors de cette liste est refusée 422 `unknown_currency`. Valeurs : `CAD`, `CHF`, `DKK`, `EUR`, `GBP`, `ISK`, `JPY`, `KWD`, `NOK`, `PLN`, `SEK`, `USD`. |
| `payment` | object | — | — |
| `payment.rail` | string · length ≤ 128 | — | — |
| `payment.mcc` | string | — | Motif : `^[0-9]{4}$`. |
| `payment.card_present` | boolean | — | — |
| `payment.network_merchant_id` | string · length ≤ 128 | — | — |
| `payment.raw_type` | string · length ≤ 128 | — | — |
| `location_hint` | object | — | — |
| `location_hint.country` | string | — | Motif : `^[A-Z]{2}$`. |
| `location_hint.city` | string · length ≤ 128 | — | — |
| `location_hint.postal_code` | string · length ≤ 128 | — | — |
| `counterparty_hint` | object | — | 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. |
| `counterparty_hint.iban` | string | — | Motif : `^[A-Z]{2}[0-9]{2}[A-Z0-9]{10,30}$`. |
| `counterparty_hint.bic` | string | — | Motif : `^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$`. |
| `account_hint` | object | — | 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`. |
| `account_hint.iban` | string | — | Motif : `^[A-Z]{2}[0-9]{2}[A-Z0-9]{10,30}$`. |
| `account_hint.external_account_id` | string · length 1–128 | — | — |
| `external_transaction_id` | string · 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. |

Exemple — Un achat carte, avec le compte pour relier une couverture

```json
{
  "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"
}
```

Exemple — Un prélèvement SEPA récurrent

```json
{
  "descriptor": "PRLV SEPA NETFLIX INTERNATIONAL",
  "direction": "outgoing",
  "occurred_at": "2026-09-05T03:10:00+02:00",
  "amount": {
    "value": "13.49",
    "currency": "EUR"
  },
  "payment": {
    "rail": "sepa_direct_debit"
  },
  "account_hint": {
    "iban": "FR7630006000011234567890189"
  }
}
```

Exemple — Une carte prépayée : le compte se nomme par votre identifiant

```json
{
  "descriptor": "APPLE.COM/BILL",
  "direction": "outgoing",
  "amount": {
    "value": "2.99",
    "currency": "EUR"
  },
  "account_hint": {
    "external_account_id": "carte-4970"
  }
}
```

#### Réponses

| Statut | Description |
| --- | --- |
| `200` | Dé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. |
| `201` | Enrichissement créé. |
| `400` | Corps illisible, ou Idempotency-Key hors alphabet. |
| `401` | Credential 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. |
| `403` | Workspace ou organisation inactif. |
| `409` | Cette 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. |
| `413` | Corps trop volumineux. |
| `415` | Le corps doit être du application/json. |
| `422` | Sché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`. |
| `429` | `rate_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`. |
| `500` | Erreur interne. |

#### Erreurs nommées par le contrat

- [`invalid_api_key`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/invalid_api_key)
- [`account_inactive`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/account_inactive)
- [`idempotency_conflict`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/idempotency_conflict)
- [`request_entity_too_large`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/request_entity_too_large)
- [`unsupported_media_type`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/unsupported_media_type)
- [`invalid_input`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/invalid_input)
- [`unknown_currency`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/unknown_currency)
- [`rate_limited`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/rate_limited)
- [`internal_error`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/internal_error)

#### Limites de débit

Cette opération consomme la limite de débit `debit_ti_v1`.

`debit_ti_v1` — operational_burst_protection · token_bucket

- par clé : 120 requêtes / minute (capacité 120, recharge 2/s)
- par workspace : 600 requêtes / minute (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`.

→ [POST /transactions/v1/enrichments](https://developer.noryo.app/fr-FR/transaction-intelligence/reference/create-enrichment.md)

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

```bash
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"}'
```

```ts
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);
```

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`.

```bash
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"
  }'
```

```ts
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);
```

### Ce que la réponse contient

Extrait de la réponse `201` du contrat pour cet exemple :

```json
{
  "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 }]
  }
}
```

À 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](https://developer.noryo.app/fr-FR/transaction-intelligence/score-semantics)).
- `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](https://developer.noryo.app/fr-FR/transaction-intelligence/enrich-a-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.

#### Paramètres

| Nom | Dans | Requis | Type | Description |
| --- | --- | --- | --- | --- |
| `enrichment_id` | path | oui | string | Motif : `^enr_[0-9a-f-]{36}$`. |

#### Réponses

| Statut | Description |
| --- | --- |
| `200` | Trouvé. |
| `400` | Hôte non déclaré. Jamais rendu pour la forme de l'identifiant. |
| `401` | Credential inutilisable. |
| `403` | Workspace ou organisation inactif. |
| `404` | Malformé, 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é. |
| `429` | `rate_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`. |
| `500` | Erreur interne. |

#### Erreurs nommées par le contrat

- [`invalid_api_key`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/invalid_api_key)
- [`account_inactive`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/account_inactive)
- [`not_found`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/not_found)
- [`rate_limited`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/rate_limited)
- [`internal_error`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/internal_error)

#### Limites de débit

Cette opération consomme la limite de débit `debit_ti_v1`.

`debit_ti_v1` — operational_burst_protection · token_bucket

- par clé : 120 requêtes / minute (capacité 120, recharge 2/s)
- par workspace : 600 requêtes / minute (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`.

→ [GET /transactions/v1/enrichments/{enrichment_id}](https://developer.noryo.app/fr-FR/transaction-intelligence/reference/get-enrichment.md)

Avec l'identifiant reçu à l'étape 4 :

```bash
curl -sS https://api.noryo.app/transactions/v1/enrichments/enr_3f2504e0-4f89-41d3-9a0c-0305e82c3301 \
  -H "Authorization: Bearer $NORYO_API_KEY"
```

```ts
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);
```

La 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](https://developer.noryo.app/fr-FR/transaction-intelligence/read-an-enrichment).

## 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

Une couverture est un fait de compte et de période, pas de transaction. Elle répond à « toutes les opérations de ce compte entre ces deux dates vous ont-elles été fournies ? » — et elle existe précisément quand il n'y a aucune transaction à envoyer : un mois sans prélèvement est le cas où la déclaration compte le plus. C'est pour cela qu'elle a sa propre ressource plutôt qu'un champ sur l'enrichissement. Sans elle, nous ne dirons jamais qu'une série est en retard : l'absence d'une échéance ne prouve rien tant que nous ignorons si le compte était observé, et `lifecycle.state` reste `unknown`. Et nous ne vérifions rien : ce que vous déclarez est enregistré comme une affirmation d'appelant (`caller_asserted`). La provenance n'est pas un champ que vous choisissez. L'appel est idempotent par construction : les intervalles qui se touchent fusionnent, donc une synchronisation horaire ne fabrique pas d'intervalles.

#### Corps de la requête

`application/json` · requis · Schéma `CoverageWindow`

| Champ | Type | Requis | Description |
| --- | --- | --- | --- |
| `account_hint` | object | oui | 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. |
| `account_hint.iban` | string | — | Motif : `^[A-Z]{2}[0-9]{2}[A-Z0-9]{10,30}$`. |
| `account_hint.external_account_id` | string · length 1–128 | — | — |
| `covered_from` | string · date | oui | — |
| `covered_to` | string · date | oui | On ne certifie pas ce qui n'a pas encore eu lieu : une date future est refusée. |
| `completeness` | string | — | `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. Valeurs : `complete`, `partial`, `unknown`. |

#### Réponses

| Statut | Description |
| --- | --- |
| `200` | Couverture enregistrée, ou fusionnée avec une voisine. |
| `400` | Corps illisible, ou hôte non déclaré. |
| `401` | Clef absente, inconnue ou révoquée. |
| `403` | Organisation ou workspace inactif. |
| `413` | Corps trop volumineux. |
| `415` | Le corps doit être du application/json. |
| `422` | Sché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. |
| `429` | `rate_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`. |
| `500` | Erreur interne. |

#### Erreurs nommées par le contrat

- [`invalid_api_key`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/invalid_api_key)
- [`account_inactive`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/account_inactive)
- [`request_entity_too_large`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/request_entity_too_large)
- [`unsupported_media_type`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/unsupported_media_type)
- [`rate_limited`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/rate_limited)
- [`internal_error`](https://developer.noryo.app/fr-FR/transaction-intelligence/errors/internal_error)

#### Limites de débit

Cette opération consomme la limite de débit `debit_ti_v1`.

`debit_ti_v1` — operational_burst_protection · token_bucket

- par clé : 120 requêtes / minute (capacité 120, recharge 2/s)
- par workspace : 600 requêtes / minute (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`.

→ [POST /transactions/v1/coverage-windows](https://developer.noryo.app/fr-FR/transaction-intelligence/reference/declare-coverage-window.md)

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`).

```bash
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"
  }'
```

```ts
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
```

Réponse `200` du contrat :

```json
{
  "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…"
}
```

`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](https://developer.noryo.app/fr-FR/transaction-intelligence/observation-coverage).

## 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](https://developer.noryo.app/fr-FR/transaction-intelligence/usage-and-api-keys).

## 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_v1` — operational_burst_protection · token_bucket

- par clé : 120 requêtes / minute (capacité 120, recharge 2/s)
- par workspace : 600 requêtes / minute (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

- [Authentification](https://developer.noryo.app/fr-FR/transaction-intelligence/authentication) : familles de clés, révocation, où ranger le secret.
- [Enrichir une transaction](https://developer.noryo.app/fr-FR/transaction-intelligence/enrich-a-transaction), [Relire un enrichissement](https://developer.noryo.app/fr-FR/transaction-intelligence/read-an-enrichment), [Couverture d'observation](https://developer.noryo.app/fr-FR/transaction-intelligence/observation-coverage).
- [Séries récurrentes](https://developer.noryo.app/fr-FR/transaction-intelligence/recurring-series) et [Relations](https://developer.noryo.app/fr-FR/transaction-intelligence/relationships) : ce que plusieurs transactions font apparaître.
- [Erreurs](https://developer.noryo.app/fr-FR/transaction-intelligence/errors), [Limites de débit](https://developer.noryo.app/fr-FR/transaction-intelligence/rate-limits), [Limites du sandbox](https://developer.noryo.app/fr-FR/transaction-intelligence/sandbox-limitations).
- La [référence](https://developer.noryo.app/fr-FR/transaction-intelligence/reference) et le [catalogue d'erreurs](https://developer.noryo.app/fr-FR/transaction-intelligence/errors), générés depuis `https://api.noryo.app/openapi.json`.

---

Page canonique : https://developer.noryo.app/fr-FR/transaction-intelligence/quickstart
