# Enrichir un descripteur de transaction

`POST /merchant/v1/enrichments` — Merchant API · [Référence API](https://developer.noryo.app/fr-FR/merchant/reference)

Authentification : `bearerCredential` (http bearer).

Rend une identité de marchand pour un descripteur bancaire brut.

**Au moins un identifiant est requis** — un en-tête `Idempotency-Key`, un
`external_transaction_id`, ou les deux. Sans aucun, la branche de déduplication est
sautée entièrement et deux appels identiques produiraient deux événements facturés que
plus rien ne distingue. L'API refuse plutôt que de laisser cela arriver.

## Paramètres

| Nom | Dans | Requis | Type | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | — | string · length 1–255 | De 1 à 255 octets, ASCII imprimable (0x21–0x7E). Noryo n'applique **aucune normalisation** : pas de rognage, pas de mise en casse, pas de traitement Unicode. La clé que vous envoyez est la clé stockée — hormis les espaces facultatifs qu'une pile HTTP retire autour de toute valeur d'en-tête, ce qui échappe à notre contrôle. Fenêtre de rejeu : **24 heures**. La même clé avec la même charge utile rejoue la réponse mémorisée octet pour octet. La même clé avec une charge utile différente rend `409`. Motif : `^[\x21-\x7E]+$`. |

## Corps de la requête

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

| Champ | Type | Requis | Description |
| --- | --- | --- | --- |
| `descriptor` | string · length ≤ 512 | oui | Le libellé bancaire brut. **Noryo ne le conserve pas** (rétention de 0 jour). |
| `amount` | number | — | Montant de la transaction. Entre dans l'identité économique. |
| `currency` | string | — | ISO 4217, **en majuscules**. `eur` est refusé — voir le guide. Motif : `^[A-Z]{3}$`. |
| `occurred_at` | string · date-time | — | En UTC, avec le suffixe `Z` : `2026-09-06T11:42:00Z`, fraction de seconde facultative. Un décalage comme `+02:00`, ou une date qui n'existe pas, est refusé en `400`. Motif : `^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,9})?Z$`. |
| `country` | string | — | ISO 3166-1 alpha-2, **en majuscules**. Entre dans l'identité économique ; **pas encore utilisé par le résolveur** — mesuré, et documenté dans le guide. Motif : `^[A-Z]{2}$`. |
| `mcc` | string | — | Motif : `^[0-9]{4}$`. |
| `external_transaction_id` | string · length ≤ 255 | — | Votre identifiant pour la transaction. Porte la déduplication économique : renvoyé avec la même entrée, il rend le résultat **courant** de la transaction (le premier, ou le dernier rafraîchissement réussi) avec `usage.deduplicated: true`, et ne facture rien. |

Exemple — Descripteur et identifiant économique

```json
{
  "descriptor": "CB MCDONALDS PARIS 4519",
  "external_transaction_id": "tx_8f21c0"
}
```

Exemple — Tous les champs acceptés

```json
{
  "descriptor": "PAIEMENT CB 1203 68 MULHOUSE CARREFOUR CITY",
  "amount": 12.4,
  "currency": "EUR",
  "occurred_at": "2026-09-06T11:42:00Z",
  "country": "FR",
  "mcc": "5814",
  "external_transaction_id": "tx_8f21c0"
}
```

## Réponses

| Statut | Description |
| --- | --- |
| `200` | Réglé. **Un `200` ne veut pas dire qu'un marchand a été trouvé** — lisez `enrichment.status`. `not_found` est une réponse normale et complète. |
| `400` | `invalid_request`, `invalid_json`, `identifier_required`, `invalid_idempotency_key`, `unsupported_canonicalization`. |
| `401` | `invalid_api_key`. Absent, mal formé, inconnu, révoqué — **toujours le même code**. Toute distinction serait un oracle. |
| `402` | `no_active_subscription`, `sandbox_quota_exhausted`. |
| `403` | `insufficient_scope`, `account_inactive`, `production_not_enabled`. |
| `409` | `idempotency_key_reuse` — même clé, charge utile différente. `transaction_id_conflict` — même `external_transaction_id`, données économiques différentes. `input_mismatch`, `refresh_not_available`. |
| `410` | `result_content_expired`. Un rejeu dédupliqué (même `external_transaction_id`, même entrée) d'une transaction dont le résultat courant a dépassé sa rétention de 90 jours. Le corps reprend l'`enrichment_id`. Rien n'est facturé ; recalculez par un rafraîchissement. |
| `413` | `request_entity_too_large`. La limite du corps est de 16 KiB, comptée en **octets**. |
| `415` | `unsupported_media_type`. Envoyez `Content-Type: application/json`. |
| `429` | `rate_limited`. Rien n'a été calculé et rien n'a été facturé. |
| `500` | `internal_error`. Le corps ne dit rien de l'incident ; citez le `request_id` au support. Reprendre avec la même `Idempotency-Key` est sans risque. |
| `503` | `engine_error`, `busy` ou `pricing_not_activated`. **Rien n'a été facturé et rien n'a été mémorisé** : reprenez avec la même `Idempotency-Key`. |

### `200` — `Enrichment`

| Champ | Type | Requis | Description |
| --- | --- | --- | --- |
| `enrichment_id` | string | — | Exemple : `"enr_7f3c1e20-…"`. |
| `merchant` | Merchant \| null | — | — |
| `merchant.name` | string | — | Exemple : `"McDonald's"`. |
| `merchant.category` | string | — | Exemple : `"alimentation.restaurant"`. |
| `merchant.logo_url` | string · uri | — | — |
| `merchant.location` | object \| null | — | **Toujours présent**, `null` s'il est inconnu. Absent et inconnu se ressemblent trop pour qu'on demande à un client de les distinguer. |
| `merchant.location.city` | string | — | Exemple : `"PARIS"`. |
| `merchant.location.postal_code` | string | — | Exemple : `"75018"`. |
| `merchant.confidence` | number · 0–1 | — | Un **score de confiance du modèle** de 0 à 1. Il ne doit **pas** être interprété comme une probabilité calibrée de justesse — Noryo a mesuré que son ordonnancement n'est pas monotone au-dessus de 0.95. Voir le guide. |
| `enrichment` | object | — | — |
| `enrichment.status` | string | — | `resolved` — identité complète et au-dessus du seuil de politique. `partial` — quelque chose manque ou est sous le seuil. `not_found` — le moteur a répondu, et sa réponse est « personne ». Valeurs : `resolved`, `partial`, `not_found`, `refresh_recalculated`. |
| `enrichment.reason` | string | — | — |
| `enrichment.environment` | string | — | Valeurs : `sandbox`, `production`. |
| `enrichment.source` | string | — | — |
| `enrichment.customized` | boolean | — | `true` quand une **règle de votre organisation** (Platform → Règles) a changé cette réponse — par exemple un nom d’affichage ou une catégorie. Le résultat Noryo lui-même est conservé intact, et une règle ne s’applique jamais hors de votre organisation ni de l’environnement de la clé. `false` sinon. Un `POST` est rendu avec les règles publiées à cet instant, et un rejeu idempotent rend ce même corps ; un `GET` applique les règles publiées **maintenant**. |
| `enrichment.rules` | array<object> | — | **Facultatif** — présent seulement quand `customized` vaut `true` : les règles qui ont changé cette réponse, chacune avec la version appliquée. Le champ que chaque règle a changé se lit dans les journaux de Platform. |
| `enrichment.rules[].id` | string · uuid | — | — |
| `enrichment.rules[].version` | integer ≥ 1 | — | — |
| `usage` | object | — | Ce que **cet appel** a coûté. |
| `usage.billable` | boolean | — | — |
| `usage.units` | integer | — | — |
| `usage.deduplicated` | boolean | — | — |
| `usage.deduplicated_by` | string \| null | — | — |

## Erreurs nommées par le contrat

- [`identifier_required`](https://developer.noryo.app/fr-FR/errors/identifier_required)
- [`invalid_idempotency_key`](https://developer.noryo.app/fr-FR/errors/invalid_idempotency_key)
- [`invalid_json`](https://developer.noryo.app/fr-FR/errors/invalid_json)
- [`invalid_request`](https://developer.noryo.app/fr-FR/errors/invalid_request)
- [`unsupported_canonicalization`](https://developer.noryo.app/fr-FR/errors/unsupported_canonicalization)
- [`invalid_api_key`](https://developer.noryo.app/fr-FR/errors/invalid_api_key)
- [`no_active_subscription`](https://developer.noryo.app/fr-FR/errors/no_active_subscription)
- [`sandbox_quota_exhausted`](https://developer.noryo.app/fr-FR/errors/sandbox_quota_exhausted)
- [`account_inactive`](https://developer.noryo.app/fr-FR/errors/account_inactive)
- [`insufficient_scope`](https://developer.noryo.app/fr-FR/errors/insufficient_scope)
- [`production_not_enabled`](https://developer.noryo.app/fr-FR/errors/production_not_enabled)
- [`idempotency_key_reuse`](https://developer.noryo.app/fr-FR/errors/idempotency_key_reuse)
- [`input_mismatch`](https://developer.noryo.app/fr-FR/errors/input_mismatch)
- [`refresh_not_available`](https://developer.noryo.app/fr-FR/errors/refresh_not_available)
- [`transaction_id_conflict`](https://developer.noryo.app/fr-FR/errors/transaction_id_conflict)
- [`result_content_expired`](https://developer.noryo.app/fr-FR/errors/result_content_expired)
- [`request_entity_too_large`](https://developer.noryo.app/fr-FR/errors/request_entity_too_large)
- [`unsupported_media_type`](https://developer.noryo.app/fr-FR/errors/unsupported_media_type)
- [`rate_limited`](https://developer.noryo.app/fr-FR/errors/rate_limited)
- [`internal_error`](https://developer.noryo.app/fr-FR/errors/internal_error)
- [`busy`](https://developer.noryo.app/fr-FR/errors/busy)
- [`engine_error`](https://developer.noryo.app/fr-FR/errors/engine_error)
- [`pricing_not_activated`](https://developer.noryo.app/fr-FR/errors/pricing_not_activated)

## Exemples de requête

cURL

```bash
IDEMPOTENCY_KEY=$(uuidgen)
curl -sS -X POST 'https://api.noryo.app/merchant/v1/enrichments' \
  -H "Authorization: Bearer $NORYO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
  "descriptor": "CB MCDONALDS PARIS 4519",
  "external_transaction_id": "tx_8f21c0"
}'
```

JavaScript

```js
// Reuse the same key when you retry this request.
const idempotencyKey = crypto.randomUUID();

const response = await fetch("https://api.noryo.app/merchant/v1/enrichments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.NORYO_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "descriptor": "CB MCDONALDS PARIS 4519",
    "external_transaction_id": "tx_8f21c0"
  }),
});
console.log(response.status, await response.text());
```

Python

```python
import os
import uuid

import requests  # pip install requests

# Reuse the same key when you retry this request.
idempotency_key = str(uuid.uuid4())

response = requests.post(
    "https://api.noryo.app/merchant/v1/enrichments",
    headers={
        "Authorization": "Bearer " + os.environ["NORYO_API_KEY"],
        "Idempotency-Key": idempotency_key,
        "Content-Type": "application/json",
    },
    json={
        "descriptor": "CB MCDONALDS PARIS 4519",
        "external_transaction_id": "tx_8f21c0",
    },
)
print(response.status_code, response.text)
```

Go

```go
package main

import (
	"crypto/rand"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	// Reuse the same key when you retry this request.
	idempotencyKey := newUUID()

	body := strings.NewReader(`{
  "descriptor": "CB MCDONALDS PARIS 4519",
  "external_transaction_id": "tx_8f21c0"
}`)
	req, err := http.NewRequest("POST", "https://api.noryo.app/merchant/v1/enrichments", body)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("NORYO_API_KEY"))
	req.Header.Set("Idempotency-Key", idempotencyKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(resp.StatusCode, string(data))
}

// newUUID returns a random (version 4) UUID: the standard library has none.
func newUUID() string {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		panic(err)
	}
	b[6] = b[6]&0x0f | 0x40
	b[8] = b[8]&0x3f | 0x80
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}
```

Java

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.UUID;

public class Main {
    public static void main(String[] args) throws Exception {
        // Reuse the same key when you retry this request.
        String idempotencyKey = UUID.randomUUID().toString();
        String body = """
            {
              "descriptor": "CB MCDONALDS PARIS 4519",
              "external_transaction_id": "tx_8f21c0"
            }""";
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.noryo.app/merchant/v1/enrichments"))
            .method("POST", HttpRequest.BodyPublishers.ofString(body))
            .header("Authorization", "Bearer " + System.getenv("NORYO_API_KEY"))
            .header("Idempotency-Key", idempotencyKey)
            .header("Content-Type", "application/json")
            .build();
        HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.statusCode() + " " + response.body());
    }
}
```

C#

```csharp
using System;
using System.Net.Http;
using System.Text;

// Reuse the same key when you retry this request.
var idempotencyKey = Guid.NewGuid().ToString();

using var client = new HttpClient();
using var request = new HttpRequestMessage(new HttpMethod("POST"), "https://api.noryo.app/merchant/v1/enrichments");
request.Headers.TryAddWithoutValidation("Authorization", "Bearer " + Environment.GetEnvironmentVariable("NORYO_API_KEY"));
request.Headers.TryAddWithoutValidation("Idempotency-Key", idempotencyKey);
var body = """
{
  "descriptor": "CB MCDONALDS PARIS 4519",
  "external_transaction_id": "tx_8f21c0"
}
""";
request.Content = new StringContent(body, Encoding.UTF8, "application/json");

using var response = await client.SendAsync(request);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
```

---

Page canonique : https://developer.noryo.app/fr-FR/merchant/reference/create-enrichment
