Aller au contenu
Sandbox

Relire un enrichissement

Relire un enrichissement par son enrichment_id pendant 30 jours, comprendre le 404 unique, et distinguer ce qui est recalculé à la lecture de ce qui reste immuable.

Voir en Markdown

Chaque enrichissement créé par POST /transactions/v1/enrichments reçoit un enrichment_id de la forme enr_<uuid>. Cet identifiant permet de le relire pendant 30 jours sans renvoyer la transaction. La relecture n'est pas une copie figée de la réponse de création : la résolution ne bouge pas, mais les relations et les séries sont recalculées au moment où vous lisez, pour refléter ce que votre workspace a appris depuis.

L'appel

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

L'appel s'authentifie avec une clé de la famille txn dans l'en-tête Authorization (voir Authentification). Il n'a ni corps ni paramètre de requête.

curl -s "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 res = await fetch(
  `https://api.noryo.app/transactions/v1/enrichments/${enrichmentId}`,
  { headers: { Authorization: `Bearer ${process.env.NORYO_API_KEY}` } },
);

const requestId = res.headers.get("noryo-request-id");

if (res.status === 404) {
  // Malformé, inconnu, expiré ou d'un autre workspace : même réponse.
  const problem = await res.json();
  console.warn("enrichissement introuvable", problem.request_id ?? requestId);
} else if (res.ok) {
  const stored = await res.json();
  console.log(stored.expires_at, stored.series?.length ?? 0);
}
TypeScript
Note

La réponse de création porte un en-tête Location (sur le 201, et sur le 200 d'un rejeu idempotent) qui donne le chemin de la ressource. Conservez-le plutôt que de reconstruire l'URL.

La réponse 200

Le corps est un StoredEnrichment (voir la référence). Sa forme diffère de la réponse de création (Enrichment) : elle ajoute expires_at, et elle ne porte ni contract_version, ni canonical_version, ni fingerprint, ni signals, ni relationships_policy. Ces cinq champs ne se lisent qu'à la création.

Schéma StoredEnrichment

  • enrichment_idstringrequis
  • created_atstring · date-timerequis
  • expires_atstring · date-timerequis

    La réponse est opérationnelle et bornée : ce n'est pas un corpus.

  • resolutionResolutionrequis

    La forme rendue par le moteur ti_v4.

  • policyobject

    Ce que « RÉSOLU » veut dire dépend de la nature de la transaction. Un virement à une personne est resolved sans qu'aucune identité ne soit créée — c'est la bonne réponse, pas un échec. Un retrait d'espèces est resolved sans marchand. Un achat carte, lui, exige sa contrepartie.

  • outcomestringrequis

    resolved : toutes les dimensions exigées par l'archétype sont là. partial : la nature est connue, une dimension exigée manque — ou des signaux se contredisent, auquel cas on refuse de conclure. unresolved : on ignore jusqu'à la nature. Les erreurs techniques (401, 409, 503) ne sont jamais une issue sémantique : elles ne portent pas ce champ.

    resolvedpartialunresolved
  • archetypestringrequis

    Le vocabulaire est français et c'est celui de la base (le vocabulaire des archétypes de résolution), conservé tel quel pour ti_v1. inconnu : la nature même de la transaction est inconnue. non_couvert : la nature est connue mais aucune politique ne la couvre (missing = [resolution_policy]).

    achat_cartefrais_bancairesimpotindetermineprelevementremboursementretrait_especesrevenutransferttransfert_personneinconnunon_couvert
  • missingarray<string>requis

    Les dimensions exigées par l'archétype et absentes.

  • policy_versionstringrequis
    ti_resolution_v1
  • normalizedobject

    Ce que le normaliseur a lu. Des chaînes, pas des scores.

  • descriptorstring
  • signalstring
  • usefulstring
  • residualstring
  • normalizer_versionstring
  • counterpartyobject· nullable

    null est un résultat, pas une panne : un inconnu honnête.

  • entity_idstring · uuid

    L'identifiant stable de l'entité dans le graphe, sous forme d'UUID nu, sans préfixe. Il est opaque : ne lui prêtez aucune structure. Le même identifiant désigne la même entité d'une réponse à l'autre et d'un workspace à l'autre (le graphe est partagé ; l'identifiant n'est pas une donnée de votre workspace). Il est cité tel quel dans series[].evidence sous la forme entite:<uuid>.

  • namestring
  • kindstring
    brandorganisation
  • evidenceobject

    Comment le motif a apparié le libellé. Deux rapports de longueur, qui sont des propriétés du texte et pas une preuve d'identité. match dit par quelle voie (jetons : un alias entier dans le libellé ; tronque : un fragment de libellé coupé par la banque). pattern est l'alias apparié, en clair.

  • matchstring
    jetonstronque
  • patternstring
  • coveragenumber ≥ 0
  • coverednumber · 0.6–1
  • semanticsCounterpartySemantics· nullable

    Quatre choses différentes qui se ressemblent, et que ce bloc refuse de confondre. Le type d'entité (counterparty.kind : marque, entité légale, intermédiaire de paiement) dit ce que c'est. L'activité (activity) dit ce que ça fait, en noms d'industrie. La catégorie (category, hors de cet objet) dit à quoi l'argent est allé, en lignes de budget. Le type de transaction (transaction_type) dit ce qui s'est passé. Aucun ne se déduit d'un autre. Et il n'existe aucun category = subscription : un abonnement n'est pas une sorte de dépense, c'est un modèle de relation commerciale. null quand le graphe ne sait rien de cette entité — une abstention, pas une panne.

  • activityobject· nullable

    Le métier, sur deux niveaux (parent puis code). On s'arrête au parent quand la source s'y arrête : un rôle utility dit « réseau » sans dire lequel, et on rend alors utilities, jamais utilities.electricity. Ce qui n'est pas su ne se complète pas.

  • codestring
  • labelstring
  • parentstring· nullable
  • recurring_modelstring· nullable

    Ce qui est habituel dans ce métier — pas un verdict sur cette transaction. Un service de streaming se vend par abonnement ; un fournisseur d'électricité facture de façon répétée sans qu'on s'abonne ; un supermarché vend à l'acte. C'est une plausibilité qui renforce un rythme déjà observé dans le workspace. Seul, il ne conclut jamais : commonly_subscription_based sur une transaction isolée ne fait pas un abonnement, et c'est la série (series[].type = subscription) qui tranche.

    commonly_subscription_basedcommonly_recurring_but_not_subscriptionusually_one_offboth_recurring_and_one_offunknownnull
  • rolesarray<string>

    Les rôles portés par l'entité dans le graphe (merchant, utility, government, financial_institution, payment_intermediary…). Un rôle n'est pas un métier, mais il en dit assez pour en dériver un quand aucun catalogue ne couvre l'entité.

  • legal_entityobject· nullable

    Une marque n'est pas une entité légale. « Netflix » est ce qu'on lit sur le relevé ; « Netflix Inc » est ce qui contracte. Les deux existent séparément dans le graphe, reliés par brand_of, parce que les fusionner ferait disparaître le jour où une marque change de propriétaire. null quand aucune société mère n'est connue — pas quand il n'y en a pas.

  • entity_idstring · uuid

    Le même espace d'identifiants que counterparty.entity_id : UUID nu, opaque, stable.

  • namestring
  • provenanceobject· nullable

    D'où vient ce qu'on affirme. nature classe la connaissance par autorité déclarée, dans cet ordre : curated (une personne l'a vérifié), deterministic (dérivé mécaniquement d'un fait déjà tenu), provider (un tiers l'annonce), inferred (nous l'avons déduit), participant (un seul client l'a dit). Cet ordre est déclaré, pas déduit d'un nombre : sans lui, un 0,95 déduit l'emporterait sur un 0,90 vérifié. strength ne départage qu'à nature égale, et n'est pas calibrée. Et aucune connaissance globale ne vient d'un seul client : ce qu'un workspace observe reste dans ce workspace. Une nature participant ne peut jamais devenir une vérité du graphe partagé.

  • naturestring
    curateddeterministicproviderinferredparticipant
  • sourcestring
  • strengthnumber · 0–1
  • calibrationstring

    Toujours non_calibree dans cette version, et c'est une contrainte, pas une convention : la valeur est un vocabulaire en base, les colonnes qui la portent y font référence, et une garde refuse tout littéral hors vocabulaire chez chaque producteur. Le jour où une calibration réelle existe, elle entrera par une migration déclarée, avec le corpus qui la défend.

    non_calibree
  • evidenceobject

    Comment la provenance a été établie. Les clefs varient : derivation (categorie_de_depense_vers_activite, role_vers_activite…), categorie_source ou role, et note, une phrase constante en français. Aucune de ces valeurs n'est un nombre.

  • counterparty_typestring
  • counterparty_notestring· nullable

    Dit pourquoi aucune identité n'a été établie.

  • categoryobject· nullable

    Une catégorie n'est pas une identité. Quand le libellé désigne un métier (« BOULANGERIE DUPONT », « PHARMACIE DE LA GARE ») et non une enseigne connue, la réponse rend ce métier ici et laisse counterparty à null. Rendre « Boulangerie » comme contrepartie reviendrait à nommer un rayon à la place d'une entreprise. Et c'est une ligne de budget, pas un métier. Cette catégorie répond à « à quoi cet argent est-il allé », du point de vue de la personne. Ce que l'entité fait est un autre champ, counterparty.semantics.activity, et les deux ne se substituent pas : EDF vend de l'électricité (activity = utilities.electricity) et la dépense se range sous logement.electricite. source dit d'où vient la ligne : entity_graph quand elle est portée par l'enseigne reconnue, generic_trade_entry quand elle est déduite d'un mot de métier lu dans le libellé. La première est bornée par confidence.entity — une enseigne mal reconnue ne donne pas une catégorie sûre. Quand une règle de votre organisation a posé la catégorie (voir customization), source est absent : slug et label viennent alors du catalogue Noryo, et aucune des deux origines Noryo ne serait vraie.

  • slugstring
  • labelstring· nullable
  • sourcestring
    entity_graphgeneric_trade_entry
  • identity_abstentionstring· nullable

    Pourquoi le moteur a refusé de nommer, alors qu'il aurait pu. trade_not_identity : un métier a été reconnu, pas une enseigne. atm_operator_not_counterparty : sur un mouvement d'espèces, la banque reconnue exploite le distributeur — elle n'est pas en face. account_bank_not_counterparty : sur un frais bancaire, c'est la banque du compte, et la nommer n'apprend rien. person_named_not_brand : le libellé nomme une personne, et l'enseigne reconnue appartient au motif du virement. Une abstention n'est pas une panne : policy.outcome peut rester resolved si l'archétype n'exigeait pas d'entité.

  • intermediariesarray<object>

    Les facilitateurs de paiement lus dans le libellé (PAYPAL *NETFLIX en donne un). Ils ne sont jamais la contrepartie. evidence est un code et une source — plus jamais le motif d'appariement lui-même, qui est une règle interne.

  • namestringrequis
  • rolestringrequis
  • entity_idstring · uuid· nullable

    L'entité du facilitateur dans le graphe, quand elle y est.

  • carries_sub_merchantboolean
  • evidenceobjectrequis
  • codestringrequis

    Le code du facilitateur au référentiel.

  • sourcestringrequis
    referentiel_facilitateurs
  • transaction_typeobject

    Ce que la transaction signifie, par opposition à payment qui dit comment l'argent a circulé. evidence est un code de règle, pas une explication : la note interne de la règle sortait ici dans une version antérieure ; les réponses créées alors portent legacy_note_redacted.

  • canonicalstringrequis
  • evidencestringrequis

    rail_rule : le rail seul a décidé. descriptor_rule : le libellé seul. descriptor_sense_rail_mechanism : le libellé donne le sens, le rail le mécanisme, sans conflit. rail_and_descriptor_agree : les deux disent la même chose. descriptor_refines_rail / rail_refines_descriptor : l'un précise l'autre. rail_descriptor_conflict : contradiction, aucune certitude fabriquée (voir signal_conflicts). no_rule : rien ne s'applique. Le suffixe +counterparty_nature dit que la nature de la contrepartie a affiné le type.

    ^(rail_rule|descriptor_rule|descriptor_sense_rail_mechanism|rail_and_descriptor_agree|descriptor_refines_rail|rail_refines_descriptor|rail_descriptor_conflict|no_rule)(\+counterparty_nature)?$|^legacy_note_redacted$
  • contradictionstring· nullablerequis
  • paymentobject· nullable

    Ce que le moteur a compris du rail et du mécanisme de paiement.

  • railstring· nullable
  • mechanismstring· nullable
  • notestring· nullable

    Une phrase explicative constante, en français, qui rappelle la différence entre rail et transaction_type. Pas une donnée.

  • signal_conflictsarray<SignalConflict>

    Les signaux qui se contredisent. Quand il y en a, policy.outcome est partial : on refuse de conclure.

  • dimensionstringrequis
  • signalsarray<string>requis
  • resolutionstringrequis

    Une phrase, en français : comment le conflit a été arbitré.

  • resolved_tostringrequis
  • submerchant_lanestring

    L'issue de la voie « sous-marchand » (un facilitateur porte l'enseigne réelle). none : voie non ouverte. no_candidate : ouverte, aucune entité. resolved : une entité unique. ambiguous : plusieurs, abstention. Absent sur les réponses les plus anciennes, créées avant que cette voie existe.

    noneno_candidateresolvedambiguous
  • truncated_lanestring

    L'issue de la voie « libellé tronqué » (la banque a coupé le nom). Mêmes valeurs que submerchant_lane. Absent sur les réponses les plus anciennes, créées avant que cette voie existe.

    noneno_candidateresolvedambiguous
  • confidenceobject

    Le nom est conservé pour ti_v1 ; ce n'est pas une confiance. Chacun de ces nombres est une constante déclarée — posée par la personne qui a écrit le motif ou la règle, et lue telle quelle. Toute transaction qui touche le même motif reçoit le même nombre. Mesuré sur 644 libellés : la précision en fonction du seuil n'est pas monotone — 0,85 donne 0,86 de précision, 0,88 donne 0,33, 0,92 donne 0,25, 0,97 donne 0,79. Ce nombre n'ordonne pas le juste avant le faux. Ne l'utilisez ni comme probabilité ni comme seuil : utilisez policy.outcome. 0 a un sens précis par champ : abstention (entity), rôle inconnu (counterparty), conflit sans ancêtre commun (transaction_type). location vaut toujours 0 : rien n'est résolu sur cet axe dans cette version.

  • entitynumber · 0–1requis
  • counterpartynumber · 0–1requis
  • transaction_typenumber · 0–1requis
  • categorynumber · 0–1requis
  • locationnumberrequis
    0
  • calibrationstringrequis

    Toujours non_calibree dans cette version, et c'est une contrainte, pas une convention : la valeur est un vocabulaire en base, les colonnes qui la portent y font référence, et une garde refuse tout littéral hors vocabulaire chez chaque producteur. Le jour où une calibration réelle existe, elle entrera par une migration déclarée, avec le corpus qui la défend.

    non_calibree
  • timings_msobject

    Des durées, en millisecondes. Rien à voir avec une confiance.

  • normalizationnumber
  • entity_resolutionnumber
  • submerchantnumber
  • totalnumber
  • versionsobject

    Les versions qui ont produit cette réponse. Un client qui conserve un nombre doit conserver la version à côté : la même valeur, sous une autre version, peut ne plus mesurer la même chose.

  • graphstring

    L'ontologie du graphe d'entités (onto_v1).

  • rulesstring

    Les règles de type de transaction (txtype_v1).

  • enginestringrequis

    Le moteur de résolution (ti_v4).

  • resolution_policystringrequis

    La même valeur que policy.policy_version.

  • calibrationstringrequis

    Toujours non_calibree dans cette version, et c'est une contrainte, pas une convention : la valeur est un vocabulaire en base, les colonnes qui la portent y font référence, et une garde refuse tout littéral hors vocabulaire chez chaque producteur. Le jour où une calibration réelle existe, elle entrera par une migration déclarée, avec le corpus qui la défend.

    non_calibree
  • relationshipsarray<Relationship>
  • typestringrequis

    refund_of : l'achat a eu lieu, puis l'argent est revenu — les deux écritures subsistent. reversal_of : l'écriture d'origine est effacée, il ne s'est rien passé. transfer_pair : deux jambes d'un seul mouvement.

    refund_ofreversal_oftransfer_pair
  • rolestringrequis

    Le rôle de cette transaction. peer pour une relation symétrique, où ni source ni cible ne veut rien dire.

    sourcetargetpeer
  • symmetricbooleanrequis
  • counterpartstringrequis

    L'autre transaction, par son identifiant d'enrichissement.

    ^enr_[0-9a-f-]{36}$
  • match_strengthobjectrequis

    Ce n'est pas une probabilité. C'est la part des signaux corroborants disponibles pour ce type qui sont effectivement présents. calibration vaut non_calibree tant qu'aucune vérité terrain n'a certifié une lecture chiffrée de ce nombre.

  • valuenumber · 0.001–1requis

    Le dénominateur dépend du type : 5 pour refund_of, 4 pour reversal_of, 2 pour transfer_pair. Un 0.5 de paire de virement (1/2) et un 0.5 de contre-passation (2/4) ne sont pas la même force. Lisez corroborations à côté. present ne compte que les signaux de l'ensemble propre au type : un marqueur de remboursement sur une paire de virement reste visible dans evidence mais ne corrobore pas.

  • calibrationstringrequis

    Toujours non_calibree dans cette version, et c'est une contrainte, pas une convention : la valeur est un vocabulaire en base, les colonnes qui la portent y font référence, et une garde refuse tout littéral hors vocabulaire chez chaque producteur. Le jour où une calibration réelle existe, elle entrera par une migration déclarée, avec le corpus qui la défend.

    non_calibree
  • corroborationsobjectrequis
  • presentinteger ≥ 0requis
  • possibleinteger ≥ 1requis
  • evidencearray<object>requis

    Les signaux qui fondent la relation, y compris les préconditions. Une relation qui ne peut pas dire pourquoi elle existe n'est pas créée.

  • codestringrequis
  • valuestring
  • value_secondsinteger

    Pour within_window : l'écart mesuré, en secondes. Une durée, pas une confiance.

  • window_secondsinteger· nullable

    La fenêtre du type de relation, en secondes. Une constante de politique, pas une mesure.

  • detector_versionstringrequis
  • statusstringrequis
    activesupersedeeinvalidee
  • created_atstring · date-time
  • seriesarray<Series>
  • series_idstringrequis

    Stable. Il se dérive de ce qui ne change pas — workspace, famille, contrepartie, sens, devise — jamais de la dernière occurrence ni du libellé courant. Une occurrence de plus ne crée pas une autre série.

    ^ser_[0-9a-f-]{36}$
  • typestringrequis

    recurring_payment dit « ce paiement revient », et rien de plus : un loyer et une échéance de prêt le sont aussi. subscription est une affirmation plus forte et exige son propre signal ; sans lui, la réponse reste recurring_payment. De même recurring_income couvre pension, prestation et loyer perçu — salary_series en est une spécialisation, pas un synonyme.

    recurring_paymentsubscriptionrecurring_incomesalary_series
  • impliesarray<string>

    Le type plus général que celui-ci spécialise, s'il existe.

  • statusstringrequis

    Deux occurrences font émerger, trois peuvent établir, une seule jamais. broken se calcule au moment de la lecture : une série devient en retard par le seul passage du temps, sans qu'aucune écriture n'ait lieu.

    emergingestablisheduncertainbroken
  • cadencestringrequis

    Lue au calendrier, pas en jours secs : le 31 janvier et le 28 février sont à un mois d'intervalle, et un salaire au dernier jour ouvré glisse.

    weeklybiweeklymonthlyquarterlyyearlyirregular_recurringunknown
  • occurrence_countintegerrequis
  • amount_behaviorobjectrequis

    L'égalité exacte n'est pas exigée : une facture d'électricité varie, un salaire aussi. Montants en unités mineures, en chaîne — jamais un nombre JSON.

  • kindstring
    fixednarrow_variablebroadly_variable
  • min_minor_unitsstring
  • max_minor_unitsstring
  • currencystring
  • first_seenstring · date-time
  • last_seenstring · date-time
  • expected_next_atstring · date-time· nullable

    Facultative, et c'est le point. Absente tant que la série n'est pas établie ou que la cadence est irrégulière : rendre une date sur deux observations faibles la ferait passer pour une prévision. Ce n'est pas une API de prévision.

  • match_strengthobjectrequis

    Ce n'est pas une probabilité : c'est la part des signaux corroborants présents sur ceux que ce type sait produire. Et ce n'est pas le même nombre que relationships[].match_strength : même nom, autre dénominateur, autres signaux. Cinq critères hétérogènes sur cinq — cadence calendaire, montant fixe ou étroit, au moins trois occurrences, entité connue, type spécialisé. Ils ne s'additionnent qu'ici.

  • valuenumber · 0.001–1requis
  • calibrationstringrequis

    Toujours non_calibree dans cette version, et c'est une contrainte, pas une convention : la valeur est un vocabulaire en base, les colonnes qui la portent y font référence, et une garde refuse tout littéral hors vocabulaire chez chaque producteur. Le jour où une calibration réelle existe, elle entrera par une migration déclarée, avec le corpus qui la défend.

    non_calibree
  • corroborationsobjectrequis
  • presentintegerrequis
  • possibleintegerrequis
    5
  • evidencearray<object>

    Les signaux qui fondent la série. value est une chaîne ou un entier selon le code (occurrences porte un entier).

  • codestringrequis
  • valuestring | integer· nullable
  • lifecycleSeriesLifecycle

    Un axe différent de status. status dit à quel point nous croyons que c'est une série ; lifecycle dit ce qui lui arrive maintenant. Une série très bien établie peut avoir cessé de prélever, et une série fraîche peut être parfaitement vivante. Et aucun état ne se conclut d'une absence sans couverture : si la source n'était pas observée de façon complète sur la fenêtre attendue, l'état est unknown, pas late. « Je n'ai rien vu » ne devient jamais « il n'y a rien eu » — c'est la différence entre un client qui a cessé d'envoyer et un abonnement qui a cessé de prélever.

  • statestringrequis

    active : l'échéance attendue est arrivée, ou n'est pas encore échue. late : une échéance manque ET le compte était observé. inactive : plusieurs échéances consécutives manquent sous observation complète — la série ne prélève plus, sans que nous sachions pourquoi. ended : réservé à une preuve terminale explicite. Aucune accumulation d'absences ne produit cet état : un prélèvement qui s'arrête peut être une résiliation, une suspension, une carte expirée ou un échec de prélèvement, et nous ne devinons pas laquelle. unknown : nous ne pouvons pas conclure, et reason dit pourquoi.

    activelateinactiveendedunknown
  • reasonstring
  • last_observed_onstring · date· nullable
  • expected_next_windowobject· nullable

    Une fenêtre, pas une date. Un salaire tombe vers les derniers jours ouvrés, un prélèvement SEPA vers le 5. Rendre un instant exact serait une précision que nous n'avons pas, et vous la croiriez.

  • fromstring · date
  • tostring · date
  • tolerance_daysinteger

    La demi-largeur de la fenêtre, en jours. Plus grand = moins précis. Vient du type d'ancrage, relevé au plancher de la famille de série.

  • anchorstring

    Le type d'ancrage qui a donné la fenêtre.

  • widened_to_family_floorboolean

    true si la tolérance a été relevée au plancher de la famille (5 jours pour un abonnement, 6 pour un salaire, 7 sinon).

  • consecutive_missesinteger· nullable

    Le compteur n'avance que sous couverture complète. Absent quand rien n'est attendu — ce qui n'est pas la même ignorance que « nous ne regardions pas ».

  • observation_coveragestring

    not_required : aucune échéance close, rien n'avait à être observé. complete : chaque source de la série était couverte sur la fenêtre attendue — un manque est un manque. incomplete : au moins une source ne l'était pas — l'absence ne prouve rien, state est unknown. unknown : les sources sont inconnues ou trop nombreuses pour être vérifiées.

    not_requiredcompleteincompleteunknown
  • cadence_strengthnumber· nullable

    Le même nombre que cadence_detail.strength, rendu ici pour que le cycle de vie soit lisible seul. Ce n'est pas un second score.

  • calendar_anchorany

    Le même objet que calendar_pattern (classe anchor_stability), rendu ici pour la même raison ; { type: unknown } quand aucun ancrage n'a été inféré. Absent quand aucune échéance n'est attendue. duplicate_of : series[].calendar_pattern — ce n'est pas un second score.

  • evidencearray<object>

    Présent pour ended : la preuve terminale qui l'a produit.

  • codestring
  • identity_statusstring· nullable

    Présent uniquement quand l'identité de la série a été corrigée, pour dire pourquoi state vaut unknown : ce n'est pas une ignorance économique, c'est que le verdict appartient désormais aux successeurs. L'état d'identité complet se lit dans identity, qui est un axe à part entière ; ce champ n'est qu'un renvoi, et il ne prend jamais la valeur current.

    supersedednull
  • policy_versionstringrequis
  • calibrationstringrequis

    Toujours non_calibree dans cette version, et c'est une contrainte, pas une convention : la valeur est un vocabulaire en base, les colonnes qui la portent y font référence, et une garde refuse tout littéral hors vocabulaire chez chaque producteur. Le jour où une calibration réelle existe, elle entrera par une migration déclarée, avec le corpus qui la défend.

    non_calibree
  • identitySeriesIdentity

    Un troisième axe, et il ne se mélange ni à status NI À lifecycle. status dit à quel point nous croyons que c'est une série ; lifecycle dit ce qui lui arrive économiquement ; identity dit si son nom est toujours le bon. Une série peut prélever tous les mois et avoir changé d'identité parce que nous avons compris qu'elle était en réalité deux flux, ou deux moitiés d'un seul. ET UN series_id n'est jamais réécrit ni réutilisé en silence. Quand nous corrigeons, l'ancien nom subsiste, marqué superseded, et il porte ses successeurs.

  • statusstringrequis

    current : ce nom est le nom courant de ce flux. superseded : ce nom a été remplacé par une correction d'identité. Il reste valide comme référence historique — il ne disparaîtra pas — mais les occurrences futures porteront le nom de ses successeurs.

    currentsuperseded
  • superseded_byobject· nullable

    Après une scission il n'y a pas de redirection unique, et c'est pourquoi ce champ rend une liste. Si nous découvrons qu'une série mélangeait deux flux, aucun des deux successeurs n'est « la suite » de l'ancien : la moitié des occurrences de l'ancien n'est pas dans l'un d'eux. Élire un canonique serait confortable et faux.

  • reasonstring

    split : cette identité en recouvrait plusieurs, et chacune a désormais la sienne. merge : cette identité et une autre étaient le même flux ; toutes deux sont remplacées par une identité neuve.

    splitmerge
  • atstring · date-time
  • evidencearray<string>

    Les codes de preuve qui ont autorisé la correction. Jamais un score interne, jamais un identifiant de ligne.

  • successor_series_idsarray<string>

    Un seul après une fusion, plusieurs après une scission. Jamais vide quand status vaut superseded.

  • supersedesarray<string>requis

    Le chemin de retour, et il compte autant que l'autre. Les identités que celle-ci remplace. Sans lui, l'ancien series_id que vous aviez stocké ne correspondrait plus à rien de ce que vous recevez, et vous n'auriez aucun moyen de faire le lien. Vide pour une série qui n'est née d'aucune correction.

  • membershipSeriesMembership

    D'où vient cette occurrence, et où est-elle maintenant. Quand une identité est corrigée, l'occurrence change de série : sans ce bloc, vous n'auriez aucun moyen de relier le series_id que vous aviez stocké à celui que vous recevez. Vous n'avez jamais à reconstruire le graphe vous-même.

  • original_series_idstring· nullable

    La série à laquelle cette occurrence a été rattachée la première fois. Égale à current_series_id tant qu'aucune correction n'a eu lieu.

    ^ser_[0-9a-f-]{36}$
  • current_series_idstring· nullablerequis
    ^ser_[0-9a-f-]{36}$
  • correctionsarray<object>requis

    Les déplacements successifs, du plus ancien au plus récent. Vide pour une occurrence qui n'a jamais bougé.

  • reasonstring
    splitmerge
  • from_series_idstring· nullable
    ^ser_[0-9a-f-]{36}$
  • to_series_idstring· nullable
    ^ser_[0-9a-f-]{36}$
  • atstring · date-time
  • first_observed_onstring · date

    Le premier jour où nous Avons observé cette série — pas le jour où elle a commencé. Une série peut tourner depuis cinq ans alors que nous ne l'observons que depuis trois mois. Il n'existe volontairement aucun champ started_at : nous ne le savons pas.

  • last_observed_onstring · date
  • may_predate_observationboolean

    true : notre observation commence avec la première occurrence connue, donc la série a pu commencer avant nous et rien ne le montrera. false : la première occurrence connue est antérieure au début de notre observation, donc son commencement est observé et non supposé.

  • cadence_detailobject

    À quelle fréquence l'événement se répète — et rien d'autre. La cadence se déduit de la progression des périodes calendaires, pas des écarts en jours : trois mois consécutifs sont trois mois consécutifs, que les écarts vaillent 28, 30 ou 31. Et une récurrence de 28 jours n'est pas mensuelle : elle tombe une fois par mois pendant onze mois puis deux, et son jour recule à chaque cycle. Nous ne l'appelons pas monthly — cette version n'a pas de cadence pour elle, et irregular_recurring vaut mieux qu'un faux mensuel. strength est la part des cycles attendus qui portent une occurrence. Ce n'est pas une probabilité.

  • typestring
  • strengthnumber · 0–1· nullable

    0 = aucune hypothèse calendaire retenue, pas « faible ».

  • calibrationstring

    Toujours non_calibree dans cette version, et c'est une contrainte, pas une convention : la valeur est un vocabulaire en base, les colonnes qui la portent y font référence, et une garde refuse tout littéral hors vocabulaire chez chaque producteur. Le jour où une calibration réelle existe, elle entrera par une migration déclarée, avec le corpus qui la défend.

    non_calibree
  • calendar_patternCalendarPattern

    Où, dans chaque période, la série tend à tomber — une question différente de cadence. Une série peut être franchement mensuelle en flottant entre le 1er et le 6 : sans ce champ, « monthly » se lirait comme « le même jour chaque mois », ce qui est faux pour la majorité des séries réelles. C'est aussi ce qui donne sa largeur à expected_next_window : un prélèvement toujours au 5 justifie trois jours, un loyer qui flotte en justifie huit. Appliquer la même fenêtre aux deux ferait déclarer « en retard » une série parfaitement normale.

  • typestring

    fixed_day_of_month : toujours autour du même jour. month_end : en fin de mois, quel que soit le nombre de jours — un prélèvement du 31 janvier, 28 février, 31 mars est un seul motif. last_business_day : le dernier jour ouvré, cas fréquent des salaires. business_day_adjusted : un jour nominal déplacé hors du week-end (samedi → vendredi, dimanche → lundi). Nous NE modélisons pas les jours fériés : ni français, ni target, ni bancaires. Un report de férié est absorbé par la tolérance de la fenêtre, pas reconnu — prétendre connaître un calendrier que nous n'avons pas serait pire que de l'ignorer. fixed_weekday : toujours le même jour de la semaine. flexible_calendar_window : une occurrence par période, sans jour stable. unknown : rien d'identifiable.

    fixed_day_of_monthmonth_endlast_business_daybusiness_day_adjustedfixed_weekdayflexible_calendar_windowunknown
  • nominal_dayinteger· nullable
  • weekdayinteger· nullable
  • day_rangearray<integer>· nullable
  • weekend_shiftsinteger· nullable
  • strengthnumber · 0–1

    Une constante par type d'ancrage, ou une formule linéaire sur l'étendue des jours pour fixed_day_of_month et flexible_calendar_window. 0 pour unknown. Un 0,33 fixed_day_of_month et un 0,3 flexible_calendar_window ne se comparent pas — et flexible_calendar_window change de règle avec la cadence (constante 0,3 en hebdomadaire, 1 - etendue/10 en mensuel).

  • calibrationstring

    Toujours non_calibree dans cette version, et c'est une contrainte, pas une convention : la valeur est un vocabulaire en base, les colonnes qui la portent y font référence, et une garde refuse tout littéral hors vocabulaire chez chaque producteur. Le jour où une calibration réelle existe, elle entrera par une migration déclarée, avec le corpus qui la défend.

    non_calibree
  • history_coverageobjectrequis

    Ce que nous pouvons voir, et ce que nous ne pouvons pas. insufficient signifie « notre mémoire est trop courte pour en juger », jamais « il n'y a pas d'occurrence plus ancienne ». Et il y a deux mémoires, qu'il ne faut pas confondre. raw_days (30) dit combien de temps la transaction reste lisible ; derived_days (400) dit combien de temps ce que nous avons appris d'une série lui survit. Une transaction purgée reste purgée — cette API ne la ressuscite jamais — mais la série se souvient qu'elle a eu lieu. C'est ce qui rend une cadence mensuelle confirmable alors que la transaction de janvier n'existe plus. observed_days est ce que nous avons effectivement vu : un client qui lit « monthly / sufficient » peut vérifier que la conclusion ne sort pas de trente jours.

  • observed_fromstring · date
  • observed_tostring · date
  • raw_daysinteger

    Durée de vie de la transaction brute. Passé ce délai elle est physiquement supprimée et rien ne la rend.

  • derived_daysinteger

    Durée de vie de la mémoire de série, comptée depuis la dernière occurrence. Sans nouvelle occurrence, l'état dérivé est physiquement supprimé à l'échéance et une occurrence ultérieure ouvre une série neuve.

  • observed_daysinteger

    Étendue réellement observée, de la première à la dernière occurrence connue.

  • retention_limit_daysintegerrequis

    Conservé pour compatibilité : identique à raw_days.

  • occurrence_countintegerrequis
  • cadencestring
  • sufficiencystringrequis

    Répond à une seule question : peut-on croire la cadence ? Pas à « sait-on quand ça a commencé », qui est le champ suivant.

    sufficientlimitedinsufficient
  • reasonstring
  • first_occurrence_reasonstring

    Dit pourquoi first_occurrence_bounded vaut ce qu'il vaut.

  • first_occurrence_boundedboolean

    Une question différente de sufficiency, et qui était confondue avec elle. true : notre observation de cette série commence avec la première occurrence que nous connaissons — elle a donc pu commencer avant nous, et rien ne le montrera jamais. false : la première occurrence connue est antérieure au début de notre observation, donc le commencement est observé et non supposé. Un import d'historique rend false ; une série découverte au fil de l'eau rend true.

  • detector_versionstring
  • customizationCustomization

    Présent seulement quand une règle de votre organisation a changé cette réponse (Platform, Règles). Le résultat Noryo reste conservé intact ; une règle ne vaut que pour votre organisation et l'environnement de la clef. Un POST est rendu avec les règles publiées à cet instant (un rejeu idempotent rend le même corps) ; un GET applique les règles publiées aujourd'hui. Un champ sur lequel deux règles se contredisent garde la valeur Noryo.

  • rulesarray<object>requis
  • idstring · uuidrequis
  • versioninteger ≥ 1requis
  • fieldsarray<string>requis

    Les champs que la règle a changés : contrepartie.nom (resolution.counterparty.name), categorie (resolution.category), recurrence (resolution.counterparty.semantics.recurring_model).

ChampContenuChange entre deux lectures ?
enrichment_idL'identifiant reçu à la créationNon
created_atL'instant de créationNon
expires_atL'instant à partir duquel la relecture rend 404Non
resolutionLa résolution rendue à la créationNon
relationshipsLes relations avec d'autres transactions du workspaceOui, calculées à la lecture
seriesLes séries auxquelles cette occurrence appartientOui, calculées à la lecture

La réponse porte aussi les en-têtes noryo-request-id (à citer au support), noryo-ratelimit-limit et noryo-ratelimit-remaining. Extrait réduit aux champs d'identité, après une scission :

{
  "enrichment_id": "enr_3f2504e0-4f89-41d3-9a0c-0305e82c3301",
  "created_at": "2026-09-05T01:10:00Z",
  "expires_at": "2026-10-05T01:10:00Z",
  "series": [
    {
      "series_id": "ser_6ba7b811-9dad-11d1-80b4-00c04fd430c8",
      "identity": {
        "status": "superseded",
        "superseded_by": {
          "reason": "split",
          "at": "2026-09-10T22:14:00Z",
          "successor_series_ids": [
            "ser_6ba7b812-9dad-11d1-80b4-00c04fd430c8",
            "ser_6ba7b810-9dad-11d1-80b4-00c04fd430c8"
          ]
        },
        "supersedes": []
      },
      "membership": {
        "original_series_id": "ser_6ba7b811-9dad-11d1-80b4-00c04fd430c8",
        "current_series_id": "ser_6ba7b810-9dad-11d1-80b4-00c04fd430c8",
        "corrections": [
          {
            "reason": "split",
            "from_series_id": "ser_6ba7b811-9dad-11d1-80b4-00c04fd430c8",
            "to_series_id": "ser_6ba7b810-9dad-11d1-80b4-00c04fd430c8",
            "at": "2026-09-10T22:14:00Z"
          }
        ]
      }
    }
  ]
}
JSON

Le 404 unique

Quatre situations rendent exactement la même réponse 404, de type not_found :

SituationRéponse
L'identifiant est malformé404
L'identifiant est bien formé mais inconnu404
L'enrichissement a dépassé expires_at404
L'enrichissement appartient à un autre workspace404

Ce choix est délibéré : rendre un 400 sur un identifiant malformé dirait qu'un enr_<uuid> bien formé « a la bonne tête », et l'API refuse de servir d'oracle sur la forme de ses identifiants. Le seul 400 possible ici concerne l'hôte (hôte non déclaré), jamais l'identifiant. Si un enrichissement récent devrait exister, vérifiez que la clé appartient au workspace qui l'a créé.

Les autres codes sont ceux de toute l'API : 401 pour une credential inutilisable, 403 pour un workspace ou une organisation inactifs, 429 (rate_limited, avec Retry-After) quand la limite de débit est atteinte, 500 en cas d'erreur interne. Chacun rend un Problem avec un request_id. Voir Erreurs et le catalogue généré.

Ce qui peut changer entre la création et une relecture

relationships et series sont calculées à la lecture, pas figées à l'écriture. L'achat du lundi ignore le remboursement du vendredi au moment où il répond ; c'est la relecture qui le montre.

Relations. Une transaction enrichie plus tard peut créer une relation avec celle-ci. relationships porte alors le lien (refund_of, reversal_of ou transfer_pair), avec role, counterpart et status. Voir Relations.

Statut et cycle de vie d'une série. La troisième occurrence d'hier a pu faire passer une série d'emerging à established, et la première transaction doit le refléter. broken se calcule aussi à la lecture : une série devient en retard par le seul passage du temps, sans qu'aucune écriture n'ait lieu. lifecycle.state suit la même logique, selon les échéances et la couverture d'observation déclarée. Voir Séries récurrentes et Cycle de vie d'une série.

Identité d'une série. C'est le changement à prévoir si vous stockez des series_id. Une correction d'identité ne réécrit jamais un identifiant : l'ancienne série reste lisible, mais son bloc identity passe à status = superseded, et superseded_by donne reason (split ou merge), at, evidence et successor_series_ids. Après une scission, successor_series_ids en compte plusieurs : aucun successeur n'est « la suite » de l'ancien, et le contrat refuse d'en élire un. Après une fusion, il n'en compte qu'un. Chaque successeur porte en retour supersedes, la liste des identités qu'il remplace.

Appartenance de l'occurrence. Le bloc membership dit d'où vient cette occurrence et où elle est maintenant : original_series_id est la série de son premier rattachement, current_series_id celle où elle se trouve aujourd'hui, et corrections liste ses déplacements du plus ancien au plus récent, chacun avec reason, from_series_id, to_series_id et at. Sans correction, les deux identifiants sont égaux et corrections est vide. Vous n'avez jamais à reconstruire le graphe vous-même.

Quand une série a été remplacée, lifecycle.identity_status vaut superseded pour expliquer que lifecycle.state soit unknown : le verdict appartient désormais aux successeurs. Voir Scission, fusion, supersession.

Attention

Si vous stockez un series_id, relisez membership.current_series_id et identity.supersedes plutôt que de supposer que l'identifiant reçu au premier POST est toujours le courant.

Ce qui ne change pas

enrichment_id, created_at, expires_at et resolution sont immuables : la relecture rend la résolution établie à la création. Si vous conservez un nombre de la résolution, conservez resolution.versions à côté : sous une autre version, la même valeur peut ne plus mesurer la même chose. Seule nuance de forme : sur les réponses les plus anciennes, transaction_type.evidence rend legacy_note_redacted à la place d'une note interne d'une version antérieure.

Un series_id n'est jamais réécrit ni réutilisé en silence. Une identité remplacée reste lisible comme référence historique.

Relecture et rejeu diffèrent. Un rejeu par Idempotency-Key sur le POST rend la réponse d'origine telle quelle, version de moteur comprise (200). Une relecture par GET rend la résolution d'origine, mais des relations et des séries calculées aujourd'hui.

Rétention et débit

Un enrichissement est relisible 30 jours : c'est la valeur retention_limit_days (identique à raw_days) de series[].history_coverage, et expires_at donne l'échéance exacte de chacun. Passé ce délai, la transaction brute est supprimée et l'API ne la ressuscite jamais : la réponse est opérationnelle et bornée, pas un corpus. La mémoire dérivée d'une série lui survit (derived_days) : une cadence mensuelle reste confirmable quand la transaction de janvier n'existe plus. Voir Limites du sandbox.

Cette lecture compte dans les limites de débit opérationnelles, comme un POST. Un 404 rendu après une credential valide consomme un jeton ; un 401 n'en consomme aucun. Les valeurs sont dans Limites de débit. Sur Platform, la page Usage compte ces lectures dans leur propre famille.

Publié le 2026-09-17 11:19 UTC (version 1).

Cette page vous a-t-elle aidé ?