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

# Uploading files

> Import transaction data into Matcher from CSV, JSON, XML, or banking formats like camt.053 — preview column detection, then upload files to a source.

This guide covers how to import transaction data from external sources into Matcher for reconciliation.

## Supported formats

***

Matcher accepts transaction files in three general-purpose formats:

* **CSV**: Comma-separated values with headers. Most common for bank exports.
* **JSON**: Array of transaction objects. Best for API integrations.
* **XML**: Structured elements. Common for enterprise systems.

Beyond these, the upload endpoint also accepts specialized banking formats such as `camt053` and the namespaced descriptor keys from the format catalog (CNAB, acquirer layouts) — see [Import formats](/en/matcher/imports/matcher-import-formats) for the full catalog.

## File structure requirements

***

Each file must contain transaction records with fields that can be mapped to Matcher's internal schema.

### Required fields

Every transaction must have these fields (or mappable equivalents):

| Field            | Type          | Description                               |
| ---------------- | ------------- | ----------------------------------------- |
| `transaction_id` | String        | Unique identifier within the source       |
| `amount`         | Decimal       | Transaction amount (positive or negative) |
| `currency`       | String        | ISO 4217 currency code                    |
| `date`           | Date/DateTime | Transaction date                          |

### Optional fields

| Field          | Type   | Description                            |
| -------------- | ------ | -------------------------------------- |
| `reference`    | String | External reference or description      |
| `counterparty` | String | Other party in the transaction         |
| `type`         | String | Transaction type (credit, debit, etc.) |
| `metadata`     | Object | Additional custom fields               |

## Format examples

***

### CSV

**CSV Requirements:**

* First row must be column headers
* UTF-8 encoding
* Comma delimiter (configurable)
* Quote fields containing commas or newlines

**Code example**

```csv theme={null}
 transaction_id,amount,currency,date,reference,type
 BANK-2024-001,1500.00,USD,2024-01-15,Invoice #1234,credit
 BANK-2024-002,-250.00,USD,2024-01-15,Service fee,debit
 BANK-2024-003,3200.50,USD,2024-01-16,Customer payment,credit
 BANK-2024-004,-89.99,USD,2024-01-16,Subscription,debit
```

### JSON

**JSON Requirements:**

* Root element must be an array
* Consistent field names across objects
* UTF-8 encoding

**Code example**

```json theme={null}
[
  {
    "transaction_id": "BANK-2024-001",
    "amount": 1500.0,
    "currency": "USD",
    "date": "2024-01-15",
    "reference": "Invoice #1234",
    "type": "credit"
  },
  {
    "transaction_id": "BANK-2024-002",
    "amount": -250.0,
    "currency": "USD",
    "date": "2024-01-15",
    "reference": "Service fee",
    "type": "debit"
  }
]
```

### XML

**XML Requirements:**

* Valid XML with declaration
* Root element containing transaction elements
* UTF-8 encoding

**Code example**

```xml theme={null}
  <?xml version="1.0" encoding="UTF-8"?>
  <transactions>
    <transaction>
      <transaction_id>BANK-2024-001</transaction_id>
      <amount>1500.00</amount>
      <currency>USD</currency>
      <date>2024-01-15</date>
     <reference>Invoice #1234</reference>
      <type>credit</type>
    </transaction>
    <transaction>
      <transaction_id>BANK-2024-002</transaction_id>
      <amount>-250.00</amount>
      <currency>USD</currency>
      <date>2024-01-15</date>
      <reference>Service fee</reference>
      <type>debit</type>
      </transaction>
  </transactions>
```

## Upload via API

***

Use the import endpoint to upload transaction files.

### Preview before uploading

Before committing a file for ingestion, you can preview it to verify column detection and sample data. This helps catch field mapping issues early.

```bash cURL theme={null}
curl -X POST "https://api.matcher.example.com/v1/imports/contexts/{contextId}/sources/{sourceId}/preview" \
 -H "Authorization: Bearer $TOKEN" \
 -F "file=@bank_statement_january.csv" \
 -F "max_rows=5"
```

#### Response

```json theme={null}
{
  "columns": ["transaction_id", "amount", "currency", "date", "reference"],
  "sampleRows": [
    ["BANK-2024-001", "1500.00", "USD", "2024-01-15", "Invoice #1234"],
    ["BANK-2024-002", "-250.00", "USD", "2024-01-15", "Service fee"]
  ],
  "rowCount": 2,
  "format": "csv"
}
```

<Tip>
  API Reference: [Preview file](/en/reference/matcher/preview-upload)
</Tip>

### Single file upload

```bash cURL theme={null}
curl -X POST "https://api.matcher.example.com/v1/imports/contexts/{contextId}/sources/{sourceId}/upload" \
 -H "Authorization: Bearer $TOKEN" \
 -F "format=csv" \
 -F "file=@bank_statement_january.csv"
```

<Info>
  Send the `format` field **before** the `file` part. If `file` arrives first, the format is inferred from the filename extension for `.csv` and `.json` only; `.xml` is never inferred (it is a format family — plain XML, camt.053 — so the explicit `format` field is required, otherwise the upload is rejected). Upload returns **202 Accepted** with the created job.
</Info>

<Tip>API Reference: [Upload file](/en/reference/matcher/upload-transaction-file)</Tip>

#### Response

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "contextId": "969a11cd-6b7d-4e71-b82b-0828e0603149",
  "sourceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "QUEUED",
  "fileName": "bank_statement_january.csv",
  "totalRows": 0,
  "persistedRows": 0,
  "droppedDuplicateRows": 0,
  "failedRows": 0,
  "failureRatePercent": 0,
  "completedWithErrors": false,
  "createdAt": "2024-01-20T10:30:00Z"
}
```

### Check import status

```bash cURL theme={null}
curl -X GET https://api.matcher.example.com/v1/imports/contexts/{contextId}/jobs/{jobId} \
 -H "Authorization: Bearer $TOKEN"
```

<Tip>
  API Reference: [Get import status](/en/reference/matcher/retrieve-ingestion-job)
</Tip>

#### Response (Processing)

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "contextId": "969a11cd-6b7d-4e71-b82b-0828e0603149",
  "sourceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "PROCESSING",
  "fileName": "bank_statement_january.csv",
  "totalRows": 1250,
  "startedAt": "2024-01-20T10:30:05Z"
}
```

#### Response (Completed)

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "contextId": "969a11cd-6b7d-4e71-b82b-0828e0603149",
  "sourceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "COMPLETED",
  "completedWithErrors": true,
  "fileName": "bank_statement_january.csv",
  "totalRows": 1250,
  "persistedRows": 1233,
  "droppedDuplicateRows": 12,
  "failedRows": 5,
  "failureRatePercent": 1,
  "reviewRows": 0,
  "diagnosis": "",
  "startedAt": "2024-01-20T10:30:05Z",
  "completedAt": "2024-01-20T10:30:45Z"
}
```

<Note>
  Per-row parse/normalization errors are **not** embedded in the job. When `completedWithErrors` is `true` (or the job `FAILED`), fetch the details from `GET /v1/imports/contexts/{contextId}/jobs/{jobId}/errors` (capped at 100 stored rows, with `totalErrors`/`truncated` accounting). For a wholesale `FAILED` job, `diagnosis` carries a safe one-line reason.
</Note>

### Import job status values

| Status       | Description                                                        |
| ------------ | ------------------------------------------------------------------ |
| `QUEUED`     | Job received, awaiting a worker                                    |
| `PROCESSING` | File is being parsed and normalized                                |
| `COMPLETED`  | Import finished (check `completedWithErrors` for partial failures) |
| `FAILED`     | Import aborted wholesale (see `diagnosis`)                         |

## Validation and error handling

***

Matcher validates uploaded files at multiple stages.

### Validation stages

<Steps>
  <Step title="Format Validation">
    Verifies the file is valid CSV, JSON, or XML with correct structure.
  </Step>

  <Step title="Schema Validation">
    Checks that required fields are present and match the configured field map.
  </Step>

  <Step title="Data Type Validation">
    Validates amounts are valid decimals, dates are parseable, currencies are valid ISO codes.
  </Step>

  <Step title="Business Rule Validation">
    Applies context-specific rules like date ranges, amount limits, etc.
  </Step>
</Steps>

### Common validation errors

| Error                    | Cause                           | Solution                                        |
| ------------------------ | ------------------------------- | ----------------------------------------------- |
| `INVALID_FORMAT`         | File cannot be parsed           | Check file encoding and structure               |
| `MISSING_REQUIRED_FIELD` | Required field not found        | Verify field mapping configuration              |
| `INVALID_AMOUNT`         | Amount not a valid number       | Check for currency symbols or commas in numbers |
| `INVALID_DATE`           | Date cannot be parsed           | Use ISO 8601 format (YYYY-MM-DD)                |
| `UNKNOWN_CURRENCY`       | Currency code not recognized    | Use ISO 4217 codes (USD, EUR, BRL)              |
| `DATE_OUT_OF_RANGE`      | Date before/after allowed range | Check context date boundaries                   |

### Handling errors

By default, valid rows are imported even if some rows have errors. Configure error handling behavior through context settings or handle errors after import completion by reviewing the job status response.

## Duplicate detection

***

Matcher automatically detects and handles duplicate transactions to prevent double-counting.

### How duplicates are detected

Duplicates are identified by the row's dedup key within a source:

* `source_id`
* `external_id` (the source-system transaction identifier)

If a row repeats that key—within the same upload or against already-persisted data—it is treated as a duplicate.

### Duplicate handling options

Set the `duplicate_policy` key in the source `config` to control handling:

| Policy                 | Behavior                                                                                                                                 |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `KEEP_FIRST` (default) | Keeps the first occurrence and silently drops repeats, counted in `droppedDuplicateRows`                                                 |
| `REJECT`               | Turns each repeated row into an ingestion row error, counted in `failedRows`                                                             |
| `FLAG_AS_EXCEPTION`    | Drops the repeat and raises a `DUPLICATE_TRANSACTION` exception on the surviving transaction (subset reported as `flaggedDuplicateRows`) |

When the key is absent, `KEEP_FIRST` applies.

### Viewing duplicate details

The import summary shows how many duplicates were found:

```json theme={null}
{
  "totalRows": 1000,
  "persistedRows": 950,
  "droppedDuplicateRows": 50,
  "failedRows": 0,
  "failureRatePercent": 0
}
```

## Batch uploads

***

For large reconciliation jobs, you can upload multiple files in sequence.

### Upload multiple files

```bash theme={null}
# Upload bank statement
curl -X POST "https://api.matcher.example.com/v1/imports/contexts/{contextId}/sources/{bankSourceId}/upload" \
 -H "Authorization: Bearer $TOKEN" \
 -F "file=@bank_january.csv" \
 -F "format=csv"

# Upload ledger export
curl -X POST "https://api.matcher.example.com/v1/imports/contexts/{contextId}/sources/{ledgerSourceId}/upload" \
 -H "Authorization: Bearer $TOKEN" \
 -F "file=@ledger_january.csv" \
 -F "format=csv"
```

### Wait for all imports

Before running matching, ensure all imports are complete:

```bash theme={null}
# List imports for context
curl -X GET "https://api.matcher.example.com/v1/imports/contexts/{contextId}/jobs" \
 -H "Authorization: Bearer $TOKEN"
```

## Search uploaded transactions

***

After importing files, you can search across all transactions in a context to verify data quality or investigate specific records.

```bash cURL theme={null}
curl -X GET "https://api.matcher.example.com/v1/imports/contexts/{contextId}/transactions/search?q=Invoice&amount_min=1000&status=UNMATCHED" \
 -H "Authorization: Bearer $TOKEN"
```

#### Response

```json theme={null}
{
  "items": [
    {
      "id": "019c96a0-2a10-7dfe-b5c1-8a1b2c3d4e5f",
      "sourceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "amount": "1500.00",
      "currency": "USD",
      "date": "2024-01-15T00:00:00Z",
      "description": "Invoice #1234",
      "status": "UNMATCHED"
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0
}
```

<Tip>
  API Reference: [Search transactions](/en/reference/matcher/search-transactions)
</Tip>

Supported filters include `amount_min`, `amount_max`, `date_from`, `date_to`, `currency`, `source_id`, `status`, and free-text search via the `q` parameter.

## Best practices

***

<AccordionGroup>
  <Accordion title="Validate files before upload">
    Check file format and encoding locally before uploading. This catches obvious errors faster.

    ```bash theme={null}
    # Check CSV is valid
    head -5 transactions.csv

    # Check encoding
    file transactions.csv
    ```
  </Accordion>

  <Accordion title="Use consistent date formats">
    Standardize on ISO 8601 format (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SSZ`) across all sources to avoid parsing issues.
  </Accordion>

  <Accordion title="Include transaction IDs">
    Always include unique transaction IDs from the source system. This enables proper duplicate detection and audit trails.
  </Accordion>

  <Accordion title="Handle negative amounts consistently">
    Decide on a convention (negative for debits, positive for credits) and apply it consistently. Document this in your field mapping.
  </Accordion>

  <Accordion title="Upload incrementally for large files">
    For very large files (>50MB), consider splitting into smaller chunks by date range. This improves reliability and allows partial retries.
  </Accordion>

  <Accordion title="Set up automated uploads">
    For recurring reconciliation, automate file uploads using scheduled jobs or webhooks from source systems.

    ```bash theme={null}
    # Example: Daily upload via cron
    0 6 * * * /scripts/upload_bank_statement.sh
    ```
  </Accordion>
</AccordionGroup>

## Next steps

***

<Card title="Reviewing Matches" icon="magnifying-glass-chart" href="/en/matcher/daily-reconciliation/matcher-reviewing-matches" horizontal>
  Learn how to interpret match results and confidence scores.
</Card>

<Card title="Field Mapping" icon="arrows-left-right" href="/en/matcher/configuration/matcher-field-mapping" horizontal>
  Configure how source fields map to Matcher's schema.
</Card>
