> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reconlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Canonical Field Registry

> The shared dictionary of canonical field paths that import profiles, APIs, and adapters map into.

The canonical field registry — defined in `packages/core-model/src/canonical/field-registry.ts`
and exposed at [`GET /v1/canonical-fields`](/api-reference/imports/list-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

```
GET /v1/canonical-fields?model=<CanonicalFieldModel>&sourceType=<CanonicalFieldSourceType>&importTargetsOnly=<boolean>
```

Query parameters (`CanonicalFieldListQuery`):

| Param               | Type                                                                              | Behavior                                                                                                                                                                                    |
| ------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`             | `PaymentIntent \| RawRecord \| FlowLeg \| FlowLegReference \| ReconciliationCase` | Filter to fields on one model.                                                                                                                                                              |
| `sourceType`        | `CanonicalFieldSourceType`                                                        | Filter to fields whose `sourceTypes` includes this value.                                                                                                                                   |
| `importTargetsOnly` | `boolean` (default `false`)                                                       | When `true` **and** `sourceType` is set, returns only fields valid as *import mapping targets* for that source type (see below). When `true` without a `sourceType`, returns an empty list. |

Each item in the response (`CanonicalFieldDefinitionResponse`):

```ts theme={null}
{
  path: string;            // e.g. "PaymentIntent.sourceAmount"
  model: string;
  label: string;
  description: string;
  requirement: 'required' | 'optional' | 'system';
  storage: 'first_class' | 'json' | 'reference';
  sourceTypes: string[];
  examples?: string[];
  notes?: string;
}
```

### `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

```text theme={null}
client_transfer_report   client_internal_ledger   bank_statement
onchain_report            psp_report                manual
api_expectation
```

`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.

| Field path                          | Requirement | Storage      | Description                                                 | Examples / Notes                                                    |
| ----------------------------------- | ----------- | ------------ | ----------------------------------------------------------- | ------------------------------------------------------------------- |
| `PaymentIntent.externalReference`   | required    | first\_class | Client's stable business reference for the expected payment | Used for business idempotency within an organization                |
| `PaymentIntent.sourceAmount`        | required    | first\_class | Expected amount leaving the source side                     | `1000.00`                                                           |
| `PaymentIntent.sourceCurrency`      | required    | first\_class | Currency or token expected on the source side               | `USD`, `USDC`                                                       |
| `PaymentIntent.destinationAmount`   | required    | first\_class | Expected amount at the beneficiary/destination side         | `82500.00`                                                          |
| `PaymentIntent.destinationCurrency` | required    | first\_class | Currency or token expected at the destination side          | `INR`, `MXN`, `USDC`                                                |
| `PaymentIntent.paymentType`         | optional    | first\_class | Broad payment category                                      | `stablecoin`, `bank`, `cross_border`, `other`                       |
| `PaymentIntent.paymentSubtype`      | optional    | first\_class | Narrow rail, corridor, or provider subtype                  | `wire`, `swift`, `usd_mxn`, `polygon_usdc`                          |
| `PaymentIntent.direction`           | optional    | first\_class | Direction from the client's perspective                     | `debit`, `credit`                                                   |
| `PaymentIntent.effectiveDate`       | optional    | first\_class | Business effective date for the expected payment            | Not the same as ingestion time                                      |
| `PaymentIntent.beneficiaryAccount`  | optional    | first\_class | Destination account, wallet, or payout identifier           |                                                                     |
| `PaymentIntent.beneficiaryName`     | optional    | first\_class | Beneficiary display name                                    |                                                                     |
| `PaymentIntent.stablecoin`          | optional    | first\_class | Token symbol when the expectation is stablecoin-specific    | `USDC`                                                              |
| `PaymentIntent.chain`               | optional    | first\_class | Chain or network label when known                           | `polygon`                                                           |
| `PaymentIntent.references`          | optional    | json         | Structured references attached to the expected payment      | Not an import mapping target — excluded from import mapping         |
| `PaymentIntent.metadata`            | optional    | json         | Client-specific context not part of standard matching       | `cost_center`, `batch_id`, `corridor`. Not an import mapping target |

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`).

| Field path                 | Requirement | Storage      | Description                     | Examples / Notes                                                                                      |
| -------------------------- | ----------- | ------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `RawRecord.source`         | required    | first\_class | Ingestion channel               | `file`, `api`, `webhook`, `manual`                                                                    |
| `RawRecord.sourceType`     | optional    | first\_class | Business category of the source | e.g. `bank_statement`, `psp_report`, `onchain_report`                                                 |
| `RawRecord.provider`       | optional    | first\_class | Vendor or provider label        | `bridge`, `bvnk`, `icici`, `alchemy`. Not valid for `client_transfer_report`/`client_internal_ledger` |
| `RawRecord.integrationKey` | optional    | first\_class | Technical connector identity    | `bridge:webhook`, `icici:daily-xlsx`. Same source-type restriction as `provider`                      |
| `RawRecord.sourceRef`      | required    | first\_class | Stable source-side identifier   | provider id, tx hash, file row id                                                                     |
| `RawRecord.payload`        | required    | json         | Untouched source payload        | Never edited; not an import mapping target                                                            |
| `RawRecord.rowNumber`      | optional    | first\_class | Source row number for files     | Used for row-level import feedback. Not valid for `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.

| Field path                    | Requirement | Storage      | Description                                                         | Examples / Notes                                                           |
| ----------------------------- | ----------- | ------------ | ------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `FlowLeg.type`                | required    | first\_class | Type of actual money movement                                       | `provider_transfer`, `onchain_transfer`, `bank_transfer`                   |
| `FlowLeg.phase`               | optional    | first\_class | Role of the leg inside a multi-step route                           | `source`, `intermediary_in`, `transfer`, `intermediary_out`, `destination` |
| `FlowLeg.status`              | optional    | first\_class | Status of this movement leg                                         | `pending`, `confirmed`, `failed`, `reversed`, `missing`                    |
| `FlowLeg.reconciliationScope` | optional    | first\_class | Whether this leg is required, optional, or ignored for completeness | `required`, `optional`, `ignored`                                          |
| `FlowLeg.routeGroupId`        | optional    | first\_class | Groups legs that belong to one route or retry path                  |                                                                            |
| `FlowLeg.sequence`            | optional    | first\_class | Order of this leg inside a route group                              |                                                                            |
| `FlowLeg.providerTransferId`  | optional    | first\_class | High-value provider transfer identifier                             | Valid for `client_transfer_report`, `psp_report`, `manual`                 |
| `FlowLeg.txHash`              | optional    | first\_class | On-chain transaction hash                                           | Valid for `onchain_report`, `psp_report`, `manual`                         |
| `FlowLeg.chainId`             | optional    | first\_class | Numeric chain identifier                                            | `137`. Valid for `onchain_report`, `psp_report`, `manual`                  |
| `FlowLeg.fromAddress`         | optional    | first\_class | On-chain sender address                                             | Valid for `onchain_report`, `manual`                                       |
| `FlowLeg.toAddress`           | optional    | first\_class | On-chain receiver address                                           | Valid for `onchain_report`, `manual`                                       |
| `FlowLeg.tokenAddress`        | optional    | first\_class | Token contract address                                              | Valid for `onchain_report`, `manual`                                       |
| `FlowLeg.provider`            | optional    | first\_class | Vendor or provider label for the normalized leg                     | Not valid for `client_transfer_report`                                     |
| `FlowLeg.integrationKey`      | optional    | first\_class | Technical connector identity for the normalized leg                 | Same restriction as `provider`                                             |
| `FlowLeg.amount`              | optional    | first\_class | Amount moved on this leg                                            |                                                                            |
| `FlowLeg.currency`            | optional    | first\_class | Currency or token moved on this leg                                 | `USD`, `USDC`, `INR`                                                       |
| `FlowLeg.occurredAt`          | optional    | first\_class | When the movement happened                                          |                                                                            |
| `FlowLeg.metadata`            | optional    | json         | Extra leg context not part of standard matching                     | Not an import mapping target                                               |

`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`.

| Field path               | Requirement | Storage   | Description                                   | Examples                                                       |
| ------------------------ | ----------- | --------- | --------------------------------------------- | -------------------------------------------------------------- |
| `FlowLegReference.type`  | required    | reference | Identifier type for long-tail leg references  | `bank_reference`, `ach_trace_number`, `uetr`, `sepa_reference` |
| `FlowLegReference.value` | required    | reference | Identifier value for long-tail leg references |                                                                |

### 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`.

<Note>
  **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.
</Note>

## `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()`.

| Field path                            | Description                                                            | Written from                                                 |
| ------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------ |
| `ReconciliationCase.expectedAmount`   | Expected case amount, usually copied from `PaymentIntent.sourceAmount` | Set when the case is created                                 |
| `ReconciliationCase.actualAmount`     | Observed reconciled amount                                             | The selected leg's `amount` (`selectCaseEvaluationLeg`)      |
| `ReconciliationCase.providerFee`      | Provider fee component for delta breakdown                             | `payload.event_object.receipt.developer_fee`, or prior value |
| `ReconciliationCase.networkFee`       | Network/chain fee component for delta breakdown                        | `payload.event_object.receipt.gas_fee`, or prior value       |
| `ReconciliationCase.developerFee`     | Platform/developer fee component for delta breakdown                   | Prior value (not refreshed by the matcher today)             |
| `ReconciliationCase.fxSpread`         | FX variance/spread component for delta breakdown                       | `payload.event_object.receipt.exchange_fee`, or prior value  |
| `ReconciliationCase.roundingDelta`    | Rounding component for delta breakdown                                 | Prior value (not refreshed by the matcher today)             |
| `ReconciliationCase.unexplainedDelta` | Remaining unexplained amount after known deltas                        | `reconcile()` output — the key ops review field              |

`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

| `ImportSourceType`       | Primary downstream target                                    |
| ------------------------ | ------------------------------------------------------------ |
| `client_internal_ledger` | `PaymentIntent` and `ReconciliationCase` (expectation-first) |
| `client_transfer_report` | Usually `FlowLeg`, sometimes supporting evidence             |
| `bank_statement`         | `FlowLeg` (evidence-first)                                   |
| `psp_report`             | `FlowLeg` (evidence-first)                                   |
| `onchain_report`         | `FlowLeg` (evidence-first)                                   |
| `manual`                 | Depends on manual entry type                                 |

<CardGroup cols={2}>
  <Card title="List canonical fields" icon="table-list" href="/api-reference/imports/list-canonical-fields">
    `GET /v1/canonical-fields` API reference.
  </Card>

  <Card title="Core Concepts" icon="layer-group" href="/architecture/core-concepts">
    What each model represents in the reconciliation lifecycle.
  </Card>
</CardGroup>
