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.
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
/transactions/v1/enrichments/{enrichment_id}Relire un enrichissement rendu.
Référence Transaction IntelligenceParamètres
- enrichment_idstringpathrequis
Réponses
200Trouvé.StoredEnrichment
400Hôte non déclaré. Jamais rendu pour la forme de l'identifiant.Problem
401Credential inutilisable.Problem
403Workspace ou organisation inactif.Problem
404Malformé, inconnu, expiré, ou appartenant à un autre workspace — les quatre cas rendent la même réponse. Rendre 400 sur un identifiant malformé dirait qu'un enr_<uuid> bien formé « à la bonne tête » : c'est un oracle de forme, et il est fermé.Problem
429rate_limited : la limite opérationnelle de débit est atteinte, pour cette clef ou pour le workspace (le detail le dit). Rien n'a été calculé, rien n'a été rangé, aucun événement n'a été émis. Rejouez après Retry-After. Voir x-noryo-rate-limit.Problem
500Erreur interne.Problem
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"Shellconst 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);
}TypeScriptLa 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
resolvedsans qu'aucune identité ne soit créée — c'est la bonne réponse, pas un échec. Un retrait d'espèces estresolvedsans 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
nullest 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[].evidencesous la formeentite:<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é.
matchdit par quelle voie (jetons: un alias entier dans le libellé ;tronque: un fragment de libellé coupé par la banque).patternest 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 aucuncategory = subscription: un abonnement n'est pas une sorte de dépense, c'est un modèle de relation commerciale.nullquand le graphe ne sait rien de cette entité — une abstention, pas une panne. - activityobject· nullable
Le métier, sur deux niveaux (
parentpuiscode). On s'arrête au parent quand la source s'y arrête : un rôleutilitydit « réseau » sans dire lequel, et on rend alorsutilities, jamaisutilities.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_basedsur 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.nullquand 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.
natureclasse 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é.strengthne 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 natureparticipantne peut jamais devenir une vérité du graphe partagé. - naturestring
curateddeterministicproviderinferredparticipant - sourcestring
- strengthnumber · 0–1
- calibrationstring
Toujours
non_calibreedans 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_sourceourole, etnote, 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 souslogement.electricite.sourcedit d'où vient la ligne :entity_graphquand elle est portée par l'enseigne reconnue,generic_trade_entryquand elle est déduite d'un mot de métier lu dans le libellé. La première est bornée parconfidence.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 (voircustomization),sourceest absent :slugetlabelviennent 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.outcomepeut resterresolvedsi l'archétype n'exigeait pas d'entité. - intermediariesarray<object>
Les facilitateurs de paiement lus dans le libellé (
PAYPAL *NETFLIXen donne un). Ils ne sont jamais la contrepartie.evidenceest 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 à
paymentqui dit comment l'argent a circulé.evidenceest 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 portentlegacy_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 (voirsignal_conflicts).no_rule: rien ne s'applique. Le suffixe+counterparty_naturedit 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
railettransaction_type. Pas une donnée. - signal_conflictsarray<SignalConflict>
Les signaux qui se contredisent. Quand il y en a,
policy.outcomeestpartial: 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 : utilisezpolicy.outcome.0a un sens précis par champ : abstention (entity), rôle inconnu (counterparty), conflit sans ancêtre commun (transaction_type).locationvaut toujours0: 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_calibreedans 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_calibreedans 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.
peerpour 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.
calibrationvautnon_calibreetant 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 pourreversal_of, 2 pourtransfer_pair. Un0.5de paire de virement (1/2) et un0.5de contre-passation (2/4) ne sont pas la même force. Lisezcorroborationsà côté.presentne compte que les signaux de l'ensemble propre au type : un marqueur de remboursement sur une paire de virement reste visible dansevidencemais ne corrobore pas. - calibrationstringrequis
Toujours
non_calibreedans 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_paymentdit « ce paiement revient », et rien de plus : un loyer et une échéance de prêt le sont aussi.subscriptionest une affirmation plus forte et exige son propre signal ; sans lui, la réponse resterecurring_payment. De mêmerecurring_incomecouvre pension, prestation et loyer perçu —salary_seriesen 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.
brokense 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_calibreedans 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.
valueest une chaîne ou un entier selon le code (occurrencesporte un entier). - codestringrequis
- valuestring | integer· nullable
- lifecycleSeriesLifecycle
Un axe différent de
status.statusdit à quel point nous croyons que c'est une série ;lifecycledit 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 estunknown, paslate. « 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, etreasondit 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
truesi 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,stateestunknown.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(classeanchor_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
statevautunknown: ce n'est pas une ignorance économique, c'est que le verdict appartient désormais aux successeurs. L'état d'identité complet se lit dansidentity, qui est un axe à part entière ; ce champ n'est qu'un renvoi, et il ne prend jamais la valeurcurrent.supersedednull - policy_versionstringrequis
- calibrationstringrequis
Toujours
non_calibreedans 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 à
statusNI Àlifecycle.statusdit à quel point nous croyons que c'est une série ;lifecycledit ce qui lui arrive économiquement ;identitydit 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 UNseries_idn'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
statusvautsuperseded. - 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_idque 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_idque 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_idtant 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, etirregular_recurringvaut mieux qu'un faux mensuel.strengthest 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_calibreedans 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_monthetflexible_calendar_window.0pourunknown. Un 0,33fixed_day_of_monthet un 0,3flexible_calendar_windowne se comparent pas — etflexible_calendar_windowchange de règle avec la cadence (constante 0,3 en hebdomadaire,1 - etendue/10en mensuel). - calibrationstring
Toujours
non_calibreedans 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.
insufficientsignifie « 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_daysest 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_boundedvaut 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 rendfalse; une série découverte au fil de l'eau rendtrue. - 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).
| Champ | Contenu | Change entre deux lectures ? |
|---|---|---|
enrichment_id | L'identifiant reçu à la création | Non |
created_at | L'instant de création | Non |
expires_at | L'instant à partir duquel la relecture rend 404 | Non |
resolution | La résolution rendue à la création | Non |
relationships | Les relations avec d'autres transactions du workspace | Oui, calculées à la lecture |
series | Les séries auxquelles cette occurrence appartient | Oui, 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"
}
]
}
}
]
}JSONLe 404 unique
Quatre situations rendent exactement la même réponse 404, de type not_found :
| Situation | Réponse |
|---|---|
| L'identifiant est malformé | 404 |
| L'identifiant est bien formé mais inconnu | 404 |
L'enrichissement a dépassé expires_at | 404 |
| L'enrichissement appartient à un autre workspace | 404 |
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.
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).