packages/core-model/src/canonical/field-registry.ts
and exposed at GET /v1/canonical-fields β is the
single source of truth for βwhat fields exist on each canonical model, and which evidence sources
can populate them.β
External files, webhooks, and APIs should map into these known fields. Unknown or long-tail data
should stay in RawRecord.payload, metadata, or FlowLegReference β never invent ad-hoc
canonical columns at runtime.
Purpose
The registry gives three parts of the product a shared contract:- Import profile builders know which fields can be mapped (
fieldMappingsonImportProfile). - API and adapter code know which canonical shape to produce when normalizing a
RawRecordinto aFlowLegorPaymentIntent. - Dashboard screens know which fields to show when users configure mappings.
The /v1/canonical-fields endpoint
CanonicalFieldListQuery):
Each item in the response (
CanonicalFieldDefinitionResponse):
requirement values
requiredβ must be present for the model to be valid (e.g.PaymentIntent.externalReference,RawRecord.payload,FlowLeg.type).optionalβ may be present; enriches matching, reporting, or review.systemβ written by the matcher/evaluator, not by import mappings. AllReconciliationCase.*fields aresystem.
storage values
first_classβ a real column on the table (queryable, indexable).jsonβ stored inside aJsoncolumn (metadata,references,payload,validationErrors).referenceβ stored as a row inFlowLegReference(typed key/value identifiers).
CanonicalFieldSourceType values
api_expectation represents direct API intake (POST /v1/payment-intents,
POST /v1/evidence/provider, POST /v1/evidence/onchain) β it is excluded from
ReconciliationRule.sourceType (a rule canβt be scoped to βthe API itselfβ as an evidence source)
but is a valid sourceTypes entry for PaymentIntent fields.
PaymentIntent fields
Populated by direct API intake (api_expectation) or client_internal_ledger file imports.
Both
sourceTypes for every PaymentIntent field are ['client_internal_ledger', 'api_expectation'].
RawRecord fields
Populated for every ingested record before normalization, from any evidence source type
(client_transfer_report, client_internal_ledger, bank_statement, onchain_report,
psp_report, manual).
FlowLeg fields
Populated when the source represents actual money movement: bank statement row, PSP/provider
transfer event, on-chain transfer event, or payout confirmation.
FlowLeg.type, .phase, .status, .reconciliationScope, .routeGroupId, .sequence,
.amount, .currency, .occurredAt, and .metadata all share
sourceTypes: ['client_transfer_report', 'bank_statement', 'onchain_report', 'psp_report', 'manual'].
FlowLegReference fields β typed long-tail identifiers
Use FlowLegReference for identifiers that should be searchable but shouldnβt become new
nullable columns on FlowLeg.
Typed reference mapping target: FlowLegReference.<referenceType>
Import profiles can target FlowLegReference.<referenceType> (e.g.
FlowLegReference.bank_reference, FlowLegReference.uetr) directly. This is a synthetic mapping
target β getSupportedImportMappingTargetsForSourceType() adds it for every source type that
supports FlowLegReference.
Legacy normalization: mapping targets of the form
references.<type> (e.g.
references.bank_reference) are automatically normalized to FlowLegReference.<type> by
normalizeImportMappingTargetPath(). New mappings should use the FlowLegReference.<type> form
directly.ReconciliationCase fields β system-written, not import targets
These fields are written by the matcher/evaluator (apps/api/src/services/matcher.ts), never by
import mappings. All have requirement: 'system' and are excluded from
getSupportedImportMappingTargetsForSourceType().
sourceTypes for these fields reflects which evidence kinds can plausibly produce the underlying
fee data: providerFee/developerFee β psp_report/manual; networkFee β onchain_report/manual;
fxSpread/roundingDelta β client_transfer_report/psp_report/manual;
expectedAmount β client_internal_ledger/api_expectation; actualAmount/unexplainedDelta β
any evidence source type.
Import mapping target rules
isSupportedImportMappingTarget(sourceType, path) decides whether a path is a valid value in
ImportProfile.fieldMappings for a given sourceType. A path is valid if:
- Itβs a canonical field whose
sourceTypesincludes the requestedsourceType, and it is not in the import-mapping-excluded set (PaymentIntent.references,PaymentIntent.metadata,RawRecord.source,RawRecord.payload,RawRecord.rowNumber,FlowLeg.metadata,FlowLegReference.type,FlowLegReference.value, and everyReconciliationCase.*field), or - It normalizes (via
normalizeImportMappingTargetPath) toFlowLegReference.<referenceType>with a non-emptyreferenceType, and that source type supportsFlowLegReferenceas a mapping target.
ImportSourceType β primary downstream model
List canonical fields
GET /v1/canonical-fields API reference.Core Concepts
What each model represents in the reconciliation lifecycle.
