Skip to main content
The canonical field registry β€” defined in 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 (fieldMappings on ImportProfile).
  • API and adapter code know which canonical shape to produce when normalizing a RawRecord into a FlowLeg or PaymentIntent.
  • Dashboard screens know which fields to show when users configure mappings.

The /v1/canonical-fields endpoint

Query parameters (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. All ReconciliationCase.* fields are system.

storage values

  • first_class β€” a real column on the table (queryable, indexable).
  • json β€” stored inside a Json column (metadata, references, payload, validationErrors).
  • reference β€” stored as a row in FlowLegReference (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:
  1. It’s a canonical field whose sourceTypes includes the requested sourceType, 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 every ReconciliationCase.* field), or
  2. It normalizes (via normalizeImportMappingTargetPath) to FlowLegReference.<referenceType> with a non-empty referenceType, and that source type supports FlowLegReference as 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.