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

# File Imports

> Ingest payment expectations and provider evidence via CSV uploads driven by reusable Import Profiles.

While ReconLayer is API-first, many counterparties and back offices still operate on daily batch files — internal ledgers, bank statements, PSP settlement reports, and on-chain transfer exports. The **File Import** pipeline turns those CSVs into the same `PaymentIntent`, `RawRecord`, and `FlowLeg` records produced by the API and webhook paths, so they flow through the same [matching engine](/integrations/ingestion-flows).

<Note>
  File imports are one of the [three ways evidence enters ReconLayer](/integrations/ingestion-flows#the-three-ways-evidence-enters-reconlayer). For provider-pushed evidence, see [Inbound Webhooks](/integrations/webhooks) and [Bridge Integration](/integrations/bridge).
</Note>

## Import lifecycle

```mermaid theme={null}
flowchart LR
    A[POST /v1/import-batches] --> B{fileHash already\nseen for this org?}
    B -->|yes| C[Return existing batch\nduplicate: true]
    B -->|no| D[Upload raw file to R2\nimports/orgId/YYYY/MM/DD/fileHash/...]
    D --> E[Parse CSV rows\nvia ImportProfile.parsingRules]
    E --> F{importProfile.sourceType}
    F -->|client_internal_ledger| G[Map row -> PaymentIntent input\nUpsert PaymentIntent + ReconciliationCase]
    F -->|bank_statement / psp_report / onchain_report| H[Map row -> RawRecord + FlowLeg\nvia ingestEvidence]
    G --> I[ImportBatch totals updated\ntotalRows / validRows / warningRows / failedRows]
    H --> I
```

<Steps>
  <Step title="Upload & batch creation">
    `POST /v1/import-batches` is called with an `importProfileId`, `originalFileName`, and the file content. ReconLayer computes `fileHash = sha256:<hex>` over the content. If a batch with the same `fileHash` already exists for the organization, the existing `ImportBatch` is returned with `duplicate: true` and **no rows are reprocessed** (response is `200` instead of `201`).
  </Step>

  <Step title="Storage">
    The raw file is uploaded to object storage at `imports/${organizationId}/${YYYY}/${MM}/${DD}/${fileHash}/${originalFileName}`. This `fileStorageKey` is recorded on the `ImportBatch`.
  </Step>

  <Step title="Profile resolution">
    The referenced `ImportProfile` supplies `sourceType`, `fieldMappings` (CSV column → canonical field path), optional `valueMappings`, and optional `parsingRules` (e.g. `delimiter`, `dateFormat`).
  </Step>

  <Step title="Parsing & normalization">
    The file is parsed as CSV (row 1 = header, data rows numbered from 2). Each data row is mapped through `fieldMappings`/`valueMappings` into canonical field values, validated, and recorded.
  </Step>

  <Step title="Downstream branching">
    * If `importProfile.sourceType === 'client_internal_ledger'`, each valid row is mapped to a `PaymentIntent` input and upserted (creating a `ReconciliationCase` if new, or detecting a conflict if an existing intent with the same `externalReference` differs).
    * For `bank_statement`, `psp_report`, and `onchain_report`, each valid row is mapped to a `RawRecord` + `FlowLeg` and passed through the same `ingestEvidence` pipeline used by webhooks and polling — including matching against open cases.
  </Step>

  <Step title="Completion">
    The `ImportBatch` is updated with `totalRows`, `validRows`, `warningRows`, `failedRows`, `status`, and `completedAt`. Per-row outcomes (including validation errors) are available via `GET /v1/import-batches/{batchId}/raw-records`.
  </Step>
</Steps>

## Import Profiles

An `ImportProfile` is a reusable, named mapping configuration scoped to your organization. It tells ReconLayer:

| Field                         | Purpose                                                                                                                                                                                                                                            |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sourceType`                  | One of `client_internal_ledger`, `bank_statement`, `psp_report`, `onchain_report` (`api_expectation` and `client_transfer_report` are valid [canonical field](#canonical-field-registry) source types but **cannot** be used for import profiles). |
| `fieldMappings`               | A `Record<string, string>` mapping your CSV column headers to canonical field paths, e.g. `{"Settlement Amount": "FlowLeg.amount"}`.                                                                                                               |
| `valueMappings`               | Optional per-field value-substitution maps — e.g. mapping a provider's status strings (`"Settled"`) onto ReconLayer enums (`"confirmed"`).                                                                                                         |
| `parsingRules`                | Optional parsing configuration. Currently supports `delimiter` (single character, defaults to `,`) and `dateFormat` for date/time fields.                                                                                                          |
| `provider` / `integrationKey` | Optional — tag imported records with a provider name or integration key for downstream filtering.                                                                                                                                                  |
| `isActive`                    | Whether the profile can currently be used to create batches.                                                                                                                                                                                       |

Every `fieldMappings` target is validated against the [canonical field registry](#canonical-field-registry) for the profile's `sourceType` — mapping a column to an unsupported target for that source type returns `400 invalid_request`.

### Create an import profile

<CodeGroup>
  ```bash Bank statement profile theme={null}
  curl -X POST https://api.reconlayer.com/v1/import-profiles \
    -H "Authorization: Bearer $RECONLAYER_API_KEY" \
    -H "x-organization-id: $ORG_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Acme Bank — Daily Settlement CSV",
      "sourceType": "bank_statement",
      "provider": "acme-bank",
      "fieldMappings": {
        "Txn Date": "FlowLeg.occurredAt",
        "Txn ID": "FlowLeg.providerTransferId",
        "Amount": "FlowLeg.amount",
        "Currency": "FlowLeg.currency",
        "Reference": "FlowLegReference.external_reference"
      },
      "valueMappings": {
        "Status": {
          "Posted": "confirmed",
          "Pending": "pending",
          "Returned": "reversed"
        }
      },
      "parsingRules": {
        "delimiter": ",",
        "dateFormat": "DD/MM/YYYY HH:mm:ss"
      },
      "isActive": true
    }'
  ```

  ```bash Internal ledger profile (fieldMappings only) theme={null}
  {
    "name": "Internal Payout Ledger",
    "sourceType": "client_internal_ledger",
    "fieldMappings": {
      "Reference": "PaymentIntent.externalReference",
      "Amount": "PaymentIntent.sourceAmount",
      "Currency": "PaymentIntent.sourceCurrency",
      "Beneficiary": "PaymentIntent.beneficiaryName",
      "Beneficiary Account": "PaymentIntent.beneficiaryAccount",
      "Value Date": "PaymentIntent.effectiveDate",
      "Direction": "PaymentIntent.direction"
    },
    "parsingRules": { "dateFormat": "DD/MM/YYYY HH:mm:ss" },
    "isActive": true
  }
  ```
</CodeGroup>

The response is an `ImportProfileDetail`, which includes `supportedTargets` — the full list of canonical fields available for this `sourceType`, useful for building a mapping UI.

See [Create an Import Profile](/api-reference/imports/create-an-import-profile), [List Import Profiles](/api-reference/imports/list-import-profiles), [Get Import Profile Detail](/api-reference/imports/get-import-profile-detail), and [Update an Import Profile](/api-reference/imports/update-an-import-profile).

<Note>
  `PATCH /v1/import-profiles/{importProfileId}` re-validates any updated `fieldMappings` against the profile's existing `sourceType` — you cannot change `sourceType` and `fieldMappings` to an incompatible combination in the same request.
</Note>

## Canonical field registry

`GET /v1/canonical-fields` returns the full registry of fields ReconLayer understands, each with the models and source types it applies to. Use it to populate a column-mapping UI or to validate a profile before creating it.

```bash theme={null}
curl "https://api.reconlayer.com/v1/canonical-fields?sourceType=bank_statement&importTargetsOnly=true" \
  -H "Authorization: Bearer $RECONLAYER_API_KEY"
```

<ParamField query="sourceType" type="string">
  Filter to fields available for one `CanonicalFieldSourceType` (`client_transfer_report`, `client_internal_ledger`, `bank_statement`, `onchain_report`, `psp_report`, `manual`, `api_expectation`).
</ParamField>

<ParamField query="model" type="string">
  Filter to fields on one `CanonicalFieldModel` (`PaymentIntent`, `RawRecord`, `FlowLeg`, `FlowLegReference`, `ReconciliationCase`).
</ParamField>

<ParamField query="importTargetsOnly" type="boolean" default="false">
  When `true`, restrict to fields usable as `fieldMappings` targets in an `ImportProfile`.
</ParamField>

A sample entry:

```json theme={null}
{
  "path": "FlowLeg.amount",
  "model": "FlowLeg",
  "label": "Leg amount",
  "description": "The monetary amount moved on this leg.",
  "requirement": "required",
  "storage": "column",
  "sourceTypes": ["client_transfer_report", "bank_statement", "onchain_report", "psp_report", "manual"],
  "examples": ["1000.00", "0.5"]
}
```

`client_internal_ledger` rows map onto `PaymentIntent.*` fields (`externalReference`, `sourceAmount`, `sourceCurrency`, `destinationAmount`, `destinationCurrency`, `paymentType`, `paymentSubtype`, `direction`, `effectiveDate`, `beneficiaryAccount`, `beneficiaryName`, `stablecoin`, `chain`, `references`, `metadata`). Evidence source types (`bank_statement`, `psp_report`, `onchain_report`, plus `client_transfer_report` and `manual`) map onto `RawRecord.*` and `FlowLeg.*` fields — `FlowLeg.type`, `.phase`, `.status`, `.amount`, `.providerTransferId`, `.txHash`, `.chainId`, `.fromAddress`, `.toAddress`, `.tokenAddress`, and `FlowLegReference.*` for arbitrary key/value references.

See [List Canonical Fields](/api-reference/imports/list-canonical-fields).

## Creating an import batch

`POST /v1/import-batches` accepts the file content inline (the schema requires `fileContent`; `fileContentBase64` is accepted as an alternative encoding for binary-safe transport).

```bash theme={null}
curl -X POST https://api.reconlayer.com/v1/import-batches \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "importProfileId": "imp_profile_01h...",
    "originalFileName": "settlements-2026-06-14.csv",
    "format": "csv",
    "fileContent": "Txn Date,Txn ID,Amount,Currency,Reference,Status\n14/06/2026 09:30:00,TXN-1001,1000.00,USD,INV-2026-0099,Posted\n"
  }'
```

<ParamField body="importProfileId" type="string" required>The profile that defines field mappings and parsing rules for this file.</ParamField>
<ParamField body="originalFileName" type="string" required>Used for storage path and display.</ParamField>
<ParamField body="fileContent" type="string" required>Raw file content as a string.</ParamField>
<ParamField body="fileContentBase64" type="string">Alternative base64-encoded content.</ParamField>
<ParamField body="fileHash" type="string">Override the computed `sha256:<hex>` dedup hash.</ParamField>
<ParamField body="createdByUserId" type="string">Attribution for the dashboard activity log.</ParamField>
<ParamField body="format" type="string" default="csv">Currently only `csv` is supported.</ParamField>

Response (`201` for a new batch, `200` if the same `fileHash` was already imported):

```json theme={null}
{
  "batch": {
    "id": "imp_batch_01h...",
    "importProfileId": "imp_profile_01h...",
    "sourceType": "bank_statement",
    "provider": "acme-bank",
    "originalFileName": "settlements-2026-06-14.csv",
    "fileStorageKey": "imports/org_01h.../2026/06/14/sha256:.../settlements-2026-06-14.csv",
    "fileHash": "sha256:9f86d0...",
    "status": "completed",
    "totalRows": 1,
    "validRows": 1,
    "warningRows": 0,
    "failedRows": 0,
    "createdAt": "2026-06-14T09:30:05.000Z",
    "completedAt": "2026-06-14T09:30:06.000Z",
    "auditEvents": []
  },
  "duplicate": false
}
```

See [Create an Import Batch](/api-reference/imports/create-an-import-batch).

## Inspecting a batch and its rows

```bash theme={null}
curl https://api.reconlayer.com/v1/import-batches/imp_batch_01h... \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"
```

```bash theme={null}
curl "https://api.reconlayer.com/v1/import-batches/imp_batch_01h.../raw-records?limit=100" \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"
```

`GET /v1/import-batches` supports `limit`, `offset`, `sourceType`, and `status` (`uploaded`, `mapping_required`, `parsing`, `validating`, `processing`, `completed`, `failed`) for filtering.

Each item in `GET /v1/import-batches/{batchId}/raw-records` is an `ImportBatchRawRecordSnapshot`:

```json theme={null}
{
  "id": "rr_01h...",
  "importBatchId": "imp_batch_01h...",
  "sourceRef": "file:imp_batch_01h...:TXN-1001",
  "rowNumber": 2,
  "normalizedType": "flow_leg",
  "normalizedId": "TXN-1001",
  "validationStatus": "valid",
  "validationErrors": null,
  "receivedAt": "2026-06-14T09:30:06.000Z",
  "payload": {
    "Txn Date": "14/06/2026 09:30:00",
    "Txn ID": "TXN-1001",
    "Amount": "1000.00",
    "Currency": "USD",
    "Reference": "INV-2026-0099",
    "Status": "Posted"
  }
}
```

`normalizedType` is `payment_intent` for `client_internal_ledger` rows and `flow_leg` for evidence rows. `sourceRef` for file-derived records follows the pattern `file:{batchId}:{rowSourceRef-or-row-{rowNumber}}`, which feeds the same `(organizationId, source, sourceRef)` dedup constraint used by webhooks and polling.

See [Get Import Batch Detail](/api-reference/imports/get-import-batch-detail), [List Import Batches](/api-reference/imports/list-import-batches), and [List Import Batch Raw Records](/api-reference/imports/list-import-batch-raw-records).

## Default leg type and phase by source type

When a `bank_statement`, `psp_report`, or `onchain_report` row doesn't explicitly map `FlowLeg.type` / `FlowLeg.phase`, ReconLayer applies these defaults:

| `sourceType`              | Default `FlowLeg.type` | Default `FlowLeg.phase` |
| ------------------------- | ---------------------- | ----------------------- |
| `bank_statement`          | `bank_transfer`        | `destination`           |
| `onchain_report`          | `onchain_transfer`     | `transfer`              |
| `psp_report` (and others) | `provider_transfer`    | `intermediary_in`       |

## Handling validation failures

A row fails validation if a required canonical field is missing, a value mapping has no match, or a date/amount fails to parse against `parsingRules`. Validation failures **do not fail the whole batch**:

* Valid rows are processed normally and feed into the matching engine (or create `PaymentIntent`s).
* Failed rows are still recorded as `ImportBatchRawRecordSnapshot`s with `validationStatus: 'failed'` and a `validationErrors` object describing what went wrong.
* The batch's `totalRows`, `validRows`, `warningRows`, and `failedRows` counters reflect the outcome so you can decide whether to re-upload a corrected file.

<Tip>
  Re-uploading a corrected file produces a **new** `ImportBatch` (different `fileHash`) — it does not retroactively edit the failed rows of a previous batch. Use `GET /v1/import-batches/{batchId}/raw-records` to find the failing rows and fix them at the source before re-exporting.
</Tip>

## Date and delimiter parsing

* `parsingRules.delimiter` must be a single character; if omitted or invalid, ReconLayer defaults to `,`.
* `parsingRules.dateFormat` controls how date/time strings are normalized. ReconLayer accepts standard ISO 8601 timestamps regardless of `dateFormat`, and additionally supports `DD/MM/YYYY HH:mm:ss` when configured.
* The CSV parser handles quoted values (including embedded delimiters and quotes) per standard CSV quoting rules. Row 1 is always treated as the header row; data rows are numbered starting from 2 for `rowNumber` in raw record snapshots.

## Next steps

* [Ingestion Flows](/integrations/ingestion-flows) — how imported rows feed the matching engine alongside webhooks and API evidence.
* [Inbound Webhooks](/integrations/webhooks) and [Bridge Integration](/integrations/bridge) — for evidence that arrives in real time instead of in batch files.
* [Outbound Webhooks](/integrations/outbound-webhooks) — subscribe to `payment_intent.created` to be notified when `client_internal_ledger` imports register new expectations.
