_Docs/
Get StartedModulesPlatformDeployCookbookChangelogReference
_Stack
_Modules
  • Ledger
  • Numscript
  • Payments
  • WalletsEE
  • FlowsEE
  • ReconciliationEE
    • Concepts
    • Getting Started
    • Control Templates
    • Alerts and Evidence
    • Reconcile an Unsupported Provider
  • WebhooksEE
  1. Modules
  2. Reconciliation
  3. Getting Started
Reconciliation

Getting Started

This guide creates a control that verifies two groups of Ledger accounts remain in balance, runs it once, and shows how to inspect a discrepancy.

Prerequisites
Ledger module configured
Reconciliation 2.4.0 or later and Ledger 2.4.11 or later
A Ledger containing the accounts you want to check
API access with reconciliation:read and reconciliation:write
Account metadata or address patterns that identify both sides of the control
The Webhooks module enabled and a webhook endpoint configured if alert notifications should be delivered outside Formance

All amounts in this guide use Ledger's smallest units. For USD/2, 100 represents USD 1.00.

1. Define the financial relationship#

Assume your main ledger contains:

  • asset accounts tagged with reconciliation.category=held;
  • obligation accounts tagged with reconciliation.category=obligation.

The control should verify, per asset, that the two groups net to zero. Each term has a sign so both account sets can be compared regardless of how their balances are represented.

2. Create the rule#

Create a ledger_invariant rule with a strict zero tolerance for USD:

curl -X POST $FORMANCE_API_URL/api/reconciliation/rules \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Customer funds integrity",
    "templateKind": "ledger_invariant",
    "templateSpec": {
      "terms": [
        {
          "ledger": "main",
          "query": {
            "$match": {
              "metadata[reconciliation.category]": "held"
            }
          },
          "sign": 1
        },
        {
          "ledger": "main",
          "query": {
            "$match": {
              "metadata[reconciliation.category]": "obligation"
            }
          },
          "sign": -1
        }
      ],
      "tolerance": {
        "USD/2": 0
      }
    },
    "schedule": {
      "kind": "on_demand"
    },
    "severity": "high",
    "cadence": "daily",
    "labels": {
      "team": "treasury",
      "environment": "production"
    }
  }'
POST/api/reconciliation/rules

The response includes the rule id, its enabled state, and an explanationCEL field describing the generated financial check. Save the rule ID for the next request.

Start on demand while validating account selection and sign conventions. After a few correct runs, update the rule to a cron schedule.

3. Evaluate the rule#

Run the rule at a known historical instant:

curl -X POST $FORMANCE_API_URL/api/reconciliation/rules/<RULE_ID>/evaluate \
  -H "Content-Type: application/json" \
  -d '{
    "at": "2026-07-20T23:59:59Z",
    "safetyMargin": "0s"
  }'
POST/api/reconciliation/rules/<RULE_ID>/evaluate

This example uses "0s", so every source is read exactly at 2026-07-20T23:59:59Z. If safetyMargin were omitted, the 30-second default would make the effective read time 2026-07-20T23:59:29Z instead.

In production, the margin avoids the newest edge of the data, where a Ledger write or connector balance update may still be in flight. Use "0s" for deterministic tests, demos, or historical runs where the data is already settled and the exact instant matters.

The evaluation returns one outcome for every configured asset. A passing outcome contains a compact proof:

json
{
  "data": {
    "id": "<EVALUATION_ID>",
    "ruleID": "<RULE_ID>",
    "result": "PASS",
    "pitPerSource": {
      "ledger:main#0": "2026-07-20T23:59:59Z",
      "ledger:main#1": "2026-07-20T23:59:59Z"
    },
    "evidence": [
      {
        "fingerprint": "asset:USD/2",
        "passed": true,
        "proof": {
          "positive": "250000",
          "negative": "-250000",
          "tolerance": "0"
        }
      }
    ]
  }
}

If the groups differ, result is FAIL. The failing entry contains the full balance breakdown and an alert is opened for asset:USD/2.

4. Inspect the alert#

List active alerts for the rule using the query builder:

curl -X GET $FORMANCE_API_URL/api/reconciliation/alerts?query={"$and":[{"$match":{"ruleID":"<RULE_ID>"}},{"$match":{"status":"OPEN"}}]}
GET/api/reconciliation/alerts

The alert identifies the asset, current evidence, first and last observation times, occurrence count, and reconciliation period. Its lastEvaluationID links back to the evaluation that most recently changed it.

For a complete chronology, list the alert's events:

curl -X GET $FORMANCE_API_URL/api/reconciliation/alerts/<ALERT_ID>/events
GET/api/reconciliation/alerts/<ALERT_ID>/events

5. Move to a schedule#

Once the rule selects the right data, update it to run every hour:

curl -X PATCH $FORMANCE_API_URL/api/reconciliation/rules/<RULE_ID> \
  -H "Content-Type: application/json" \
  -d '{
    "schedule": {
      "kind": "cron",
      "expr": "0 * * * *",
      "tz": "UTC",
      "safetyMargin": "30s"
    }
  }'
PATCH/api/reconciliation/rules/<RULE_ID>

For a tick scheduled at 10:00:00, the 30-second margin reads every source at 09:59:30. This gives recent Ledger writes and Payments ingestion a short window to settle before comparison. It does not wait 30 seconds before running; it moves the financial observation time 30 seconds into the past.

If you omit safetyMargin, scheduled evaluations use the 30-second default. To disable the margin, save safetyMargin: "0s"; the explicit zero is preserved and every scheduled run reads at its exact occurrence time.

For a daily rule, alerts are grouped by the UTC day of the effective read timestamp. Use continuous cadence for one ongoing operational case, or weekly and monthly for period-based certification.

6. Pause or resume a rule#

Rules are enabled by default. Disable a rule when a control is temporarily not applicable or its sources are under maintenance:

curl -X PATCH $FORMANCE_API_URL/api/reconciliation/rules/<RULE_ID> \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": false
  }'
PATCH/api/reconciliation/rules/<RULE_ID>

Disabling a rule:

  • stops scheduled evaluations and cancels pending jobs for the previous rule revision;
  • rejects on-demand evaluation requests while the rule is disabled;
  • preserves existing evaluations, alerts, and alert timelines;
  • leaves active alerts active—they do not become resolved merely because the rule stopped running.

Resume the rule with:

curl -X PATCH $FORMANCE_API_URL/api/reconciliation/rules/<RULE_ID> \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true
  }'
PATCH/api/reconciliation/rules/<RULE_ID>

For a cron rule, Reconciliation calculates the next future occurrence when it is re-enabled. It does not create evaluations for the interval during which the rule was disabled.

7. Delete a rule#

Rule deletion is available through the API; no manual PostgreSQL operation is required:

curl -X DELETE $FORMANCE_API_URL/api/reconciliation/rules/<RULE_ID>
DELETE/api/reconciliation/rules/<RULE_ID>

A successful deletion returns 204 No Content. It permanently removes the rule and cascades to all of its evaluations, alerts, and alert-event timelines.

Deletion removes the audit history associated with the rule. Disable the rule instead when you need to stop future evaluations but retain prior evidence and resolution records. Export any required records before deleting.

Next steps#

  • Compare this template with the other control templates.
  • Learn how to acknowledge, snooze, resolve, and accept alerts.
  • Use a cash pool to reconcile external provider balances.
ConceptsControl Templates
On This Page
  • 1. Define the financial relationship
  • 2. Create the rule
  • 3. Evaluate the rule
  • 4. Inspect the alert
  • 5. Move to a schedule
  • 6. Pause or resume a rule
  • 7. Delete a rule
  • Next steps