Reconciliation

Reconciliation overview

Match ledger transactions to external records and read the result.

Reconciliation compares transactions in your ledger with records from outside it, such as a bank statement or a processor report.

You send those records, choose how they should line up, and then read which ones matched.

Before you start

A run needs three things in place:

  1. External records in Blnk’s file format. See External data.
  2. Matching rules that say which fields must agree, and how much drift is allowed. See Matching rules.
  3. A strategy for whether one record on one side splits into many on the other. See Strategies.

Batch reconciliation

Batch reconciliation matches a file you already uploaded. Use it for a scheduled job, such as a daily statement. Pass the upload_id from that upload, the rules, and the strategy.

Using the API

Call Start reconciliation.

curl -X POST "http://localhost:5001/reconciliation/start" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "upload_id": "upload_8c700d1b-09c0-4ef4-9ab1-a0decf3d0aa3",
    "strategy": "one_to_many",
    "dry_run": false,
    "grouping_criteria": "reference",
    "matching_rule_ids": [
      "rule_890bdbc4-467f-4670-8424-5667e41daf29",
      "rule_a1b2c3d4-5678-90ef-abcd-ef1234567890"
    ]
  }'
const response = await blnk.Reconciliation.run({
  upload_id: 'upload_8c700d1b-09c0-4ef4-9ab1-a0decf3d0aa3',
  strategy: 'one_to_many',
  dry_run: false,
  grouping_criteria: 'reference',
  matching_rule_ids: [
    'rule_890bdbc4-467f-4670-8424-5667e41daf29',
    'rule_a1b2c3d4-5678-90ef-abcd-ef1234567890',
  ],
});
result, _, _ := client.Reconciliation.Run(blnkgo.RunReconData{
    UploadID: "upload_8c700d1b-09c0-4ef4-9ab1-a0decf3d0aa3",
    Strategy: blnkgo.ReconciliationStrategyOneToMany,
    DryRun: false,
    GroupingCriteria: "reference",
    MatchingRuleIDs: []string{
        "rule_890bdbc4-467f-4670-8424-5667e41daf29",
        "rule_a1b2c3d4-5678-90ef-abcd-ef1234567890",
    },
})
response = blnk.reconciliation.run({
  "upload_id": "upload_8c700d1b-09c0-4ef4-9ab1-a0decf3d0aa3",
  "strategy": "one_to_many",
  "dry_run": False,
  "grouping_criteria": "reference",
  "matching_rule_ids": [
    "rule_890bdbc4-467f-4670-8424-5667e41daf29",
    "rule_a1b2c3d4-5678-90ef-abcd-ef1234567890",
  ],
})
ApiResponse<JsonNode> response = blnk.reconciliation().run(
    RunReconData.create()
        .uploadId("upload_8c700d1b-09c0-4ef4-9ab1-a0decf3d0aa3")
        .strategy("one_to_many")
        .dryRun(false)
        .groupingCriteria("reference")
        .matchingRuleIds(List.of(
            "rule_890bdbc4-467f-4670-8424-5667e41daf29",
            "rule_a1b2c3d4-5678-90ef-abcd-ef1234567890"
        ))
);
FieldDescriptionRequired
upload_idUpload returned by Upload external records.Yes
strategyone_to_one, one_to_many, or many_to_one.Yes
matching_rule_idsRules to apply. A record matches when every criterion in any one of these rules matches.Yes
grouping_criteriaField that groups the many side. Required for one_to_many and many_to_one.No
dry_runWhen true, the run stores the counts and does not write match rows or metadata. Defaults to false.No
{
  "reconciliation_id": "recon_6e6feddd-930b-4e3a-8ba1-a3eee659bb3c"
}
FieldDescription
reconciliation_idId of the run. Save it and poll Get reconciliation for status and counts.

Using the Dashboard

You can start the same batch run from the dashboard after the file and the rules exist.

Open Run reconciliation

On Reconciliations, click Run reconciliation in the top right.

Run reconciliation panel

Choose the file, rules, and strategy

Fill in the side panel, then click Run reconciliation.

  • External data: the upload you want to match.
  • Matching rules: one or more rules for this run.
  • Strategy: one_to_one, one_to_many, or many_to_one.
  • Grouping criteria: required unless the strategy is one-to-one. See Strategies.

Blnk starts the run and opens the reconciliation table. A large file can take a few minutes.

Instant reconciliation

Instant reconciliation sends the external records in the request instead of uploading a file. Use it when the records should be matched as they arrive.

Call the Instant reconciliation endpoint.

curl -X POST "http://localhost:5001/reconciliation/start-instant" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "external_transactions": [
      {
        "id": "txn1a2b3c4d5e6f7g8h9i0",
        "amount": 5.49,
        "reference": "INV-2023-002",
        "currency": "GBP",
        "description": "Card payment",
        "date": "2024-11-15T14:25:30Z",
        "source": "bank-api"
      }
    ],
    "strategy": "one_to_one",
    "matching_rule_ids": [
      "rule_04c4a59e-08a1-423b-bfae-48feebe16473"
    ]
  }'
const response = await blnk.Reconciliation.runInstant({
  external_transactions: [
    {
      id: 'txn1a2b3c4d5e6f7g8h9i0',
      amount: 5.49,
      reference: 'INV-2023-002',
      currency: 'GBP',
      description: 'Card payment',
      date: '2024-11-15T14:25:30Z',
      source: 'bank-api',
    },
  ],
  strategy: 'one_to_one',
  matching_rule_ids: [
    'rule_04c4a59e-08a1-423b-bfae-48feebe16473',
  ],
});
txnDate, _ := time.Parse(time.RFC3339, "2024-11-15T14:25:30Z")
instantResp, _, _ := client.Reconciliation.RunInstant(blnkgo.RunInstantReconData{
    ExternalTransactions: []blnkgo.ExternalTransaction{
        {
            ID: "txn1a2b3c4d5e6f7g8h9i0",
            Amount: 5.49,
            Reference: "INV-2023-002",
            Currency: "GBP",
            Description: "Card payment",
            Date: &txnDate,
            Source: "bank-api",
        },
    },
    Strategy: blnkgo.ReconciliationStrategyOneToOne,
    MatchingRuleIDs: []string{
        "rule_04c4a59e-08a1-423b-bfae-48feebe16473",
    },
})
response = blnk.reconciliation.run_instant({
  "external_transactions": [
    {
      "id": "txn1a2b3c4d5e6f7g8h9i0",
      "amount": 5.49,
      "reference": "INV-2023-002",
      "currency": "GBP",
      "description": "Card payment",
      "date": "2024-11-15T14:25:30Z",
      "source": "bank-api",
    },
  ],
  "strategy": "one_to_one",
  "matching_rule_ids": [
    "rule_04c4a59e-08a1-423b-bfae-48feebe16473",
  ],
})
ApiResponse<JsonNode> response = blnk.reconciliation().runInstant(
    RunInstantReconData.create()
        .externalTransactions(List.of(
            ExternalTransaction.create()
                .id("txn1a2b3c4d5e6f7g8h9i0")
                .amount(5.49)
                .reference("INV-2023-002")
                .currency("GBP")
                .description("Card payment")
                .date("2024-11-15T14:25:30Z")
                .source("bank-api")
        ))
        .strategy("one_to_one")
        .matchingRuleIds(List.of(
            "rule_04c4a59e-08a1-423b-bfae-48feebe16473"
        ))
);
FieldDescriptionRequired
external_transactionsRecords to match, at most 10,000. Each record can include id, amount, reference, currency, description, date, and source. A missing date is stored as the zero time.Yes
strategyone_to_one, one_to_many, or many_to_one. Any other value is accepted, and the run completes with zero counts.Yes
matching_rule_idsRules to apply. A record matches when every criterion in any one of these rules matches.Yes
grouping_criteriaField that groups the many side. Required for one_to_many and many_to_one. A value Core cannot group on does not fail this call, and the run finishes as failed.No
dry_runWhen true, the run stores the counts and does not write match rows or metadata. Defaults to false.No
{
  "reconciliation_id": "recon_6e6feddd-930b-4e3a-8ba1-a3eee659bb3c"
}
FieldDescription
reconciliation_idId of the run. The match counts are on the get response, not here.

Read a reconciliation

Both start calls return only reconciliation_id. The run begins as started, moves to in_progress, and finishes as completed or failed.

Using the API

Poll Get reconciliation until the status is completed or failed.

curl -X GET "http://localhost:5001/reconciliation/recon_6e6feddd-930b-4e3a-8ba1-a3eee659bb3c" \
  -H "X-blnk-key: <api-key>"
const response = await blnk.Reconciliation.get(
  'recon_6e6feddd-930b-4e3a-8ba1-a3eee659bb3c',
);
recon, _, _ := client.Reconciliation.Get(
    "recon_6e6feddd-930b-4e3a-8ba1-a3eee659bb3c",
)
response = blnk.reconciliation.get(
  "recon_6e6feddd-930b-4e3a-8ba1-a3eee659bb3c",
)
ApiResponse<JsonNode> response = blnk.reconciliation().get(
    "recon_6e6feddd-930b-4e3a-8ba1-a3eee659bb3c"
);
{
  "reconciliation_id": "recon_6e6feddd-930b-4e3a-8ba1-a3eee659bb3c",
  "upload_id": "upload_8c700d1b-09c0-4ef4-9ab1-a0decf3d0aa3",
  "status": "completed",
  "matched_transactions": 3,
  "unmatched_transactions": 0,
  "is_dry_run": false,
  "started_at": "2025-03-15T17:43:26.463849Z",
  "completed_at": "2025-03-15T17:43:26.508514Z"
}
FieldDescription
statusstarted, in_progress, completed, or failed.
matched_transactionsFor one_to_one, external records that matched. For one_to_many, ledger transactions inside matched groups. For many_to_one, external rows inside matched groups.
unmatched_transactionsFor one_to_one and one_to_many, external records that did not match. For many_to_one, ledger transactions that did not match.
is_dry_runAlways false. Core does not store the dry-run flag.
upload_idThe file for a batch run. An instant run uses a generated id.
started_atWhen the run was created.
completed_atWhen the run finished. Null until the status is completed.

An instant run can already be completed on the first poll. You still need this response to read the counts.

Using the Dashboard

Open the run

Open the run from the reconciliation table, then click Full info in the side panel.

Reconciliation summary

The summary shows the status, matched and unmatched counts, when the run started and finished, the reconciliation id, and the upload id.

Review matched transactions

Matched transactions lists the external records that found a ledger transaction. Click View ledger transactions to open them.

Matched transactions

Review unmatched transactions

Unmatched transactions lists external records with no ledger match. Click Export as CSV to download that list.

Unmatched transactions

Check a matched transaction

A match that is not a dry run writes reconciliation details onto the ledger transaction. Retrieve it with Get transaction and read meta_data.

{
  "meta_data": {
    "external_txn_id": "txn9z8y7x6w5v4u3t2s1",
    "reconciled": true,
    "reconciled_at": "2025-03-15T18:11:52Z",
    "reconciliation_amount": 12.75,
    "reconciliation_id": "recon_9134cb8a-a3f6-47e9-b566-4fb93fa051e7"
  }
}
FieldDescription
external_txn_idId of the external record that matched.
reconciledtrue when the transaction matched.
reconciled_atWhen the match was written.
reconciliation_amountThe external amount for one_to_one and many_to_one. For one_to_many, each ledger transaction’s own amount.
reconciliation_idRun that wrote the match.

On the transaction in the dashboard, the same keys appear in its reconciliation metadata.