Skip to Content
Reconciliation

Reconciliation

Reconciliation compares the virtual balances in our ledger with the balances that you report. Its purpose is to find a drift between the two sides within hours, before it grows. This page explains when reconciliation runs, what it compares and where you read the result.

When it runs

Reconciliation runs for every tenant on two schedules:

  • Hourly incremental run. It compares only the accounts that had movements since the last completed run. A drift is found within one hour at the latest.
  • Daily full run. It compares all the user accounts of your tenant. It covers the accounts that the incremental run skipped, and it covers a missed hourly run.

You do not need to wait for reconciliation to get an instant comparison. Every balance-sync response returns the resulting balance on our side. Compare this value with your own record (Getting started).

What it compares

The basis of the comparison is the resulting_balance field that you send in balance syncs. This field is the balance that your system shows after the movement.

For an account, we take the last resulting_balance that you sent. To this value we add the movements that happened on our side after that sync: position openings, position settlements and market void refunds. If the result is not equal to the balance in our ledger, we open a reconciliation discrepancy for the account. Balance syncs that you sent after that sync are not part of the sum, because your next resulting_balance already includes them.

A debit movement that we rejected with 409 would_overdraw always opens a discrepancy. This rejection shows that the views of the two sides already differ (FAQ).

Accounts with no resulting_balance

If you never sent a resulting_balance for an account, there is no balance to compare. Reconciliation treats this account as unverifiable. An unverifiable account is not a discrepancy: we open no discrepancy record and we send no webhook. The reconciliation_statement report does not show the unverifiable status today, so a statement with no discrepancies does not prove that every account was compared.

To make your accounts verifiable, send resulting_balance in every balance sync.

When a discrepancy is found

When reconciliation finds a discrepancy, it sends you the reconciliation.discrepancy event. The Webhook guide explains what you must do.

We do not report an open discrepancy again in every run. While an account has an open discrepancy, later runs open no new discrepancy record for it and send no new event. If the drift continues after our team resolves the discrepancy, the next run finds it again and reports it to you again.

Where you read the result

The result of reconciliation is in the reconciliation_statement report. This report is an account statement: the opening balance, the ledger movements of the period in order, the closing balance and the list of discrepancies of the period. You can request the report for one user or for the total of your tenant.

The statement for the total of your tenant has a second section: your operator book, which is the tenant_aggregate account of your tenant. The rows come in this order:

  1. opening_balance, one entry row for each movement on the user accounts, and closing_balance.
  2. aggregate_opening_balance, one aggregate_entry row for each movement on your operator book, and aggregate_closing_balance.
  3. One discrepancy row for each discrepancy of the period.

Your operator book records every movement that crosses to your side: balance syncs, the residue or shortfall that a settlement closeout leaves, and the correction shortfall credits that you absorb. The entry_type column tells these movements apart. The amount of an aggregate_entry row has the sign of your operator book. A balance sync credit to a user is negative in this section, a closeout residue is positive and a closeout shortfall is negative.

Each section has its own balances. Its closing balance is its opening balance plus its own rows. Do not add the two sections together, because a balance sync appears in both. The statement for one user has no operator book section. A statement that we generated before this section existed does not have it either, so request the period again to get it.

You can request the report in two ways:

  • From the Reports screen of the tenant portal. The Settlement screen of the portal shows the statement for the total of your tenant.
  • With POST /v1/reports, using a key that has the read:reports scope. For the statement of one user, give the external_user_id filter.

We generate the report in the background. When it is ready, the report.ready event arrives. You can also poll the status with GET /v1/reports/{id}. The report downloads in JSON or CSV format. The API reference has the details.

The checklist item

The Go-live checklist asks for one clean reconciliation run: at least one full scheduled run over your test traffic, with zero discrepancies. Reconciliation runs in test mode the same way as in live mode. You show it with a test key like this:

  1. Register users in test mode and send balance syncs. Send resulting_balance in every sync. If you do not, the accounts stay unverifiable.
  2. Open positions in test mode and watch them settle.
  3. Wait for one daily full run to pass.
  4. Request the reconciliation_statement report for the total of your tenant, for the period that the run covers. The discrepancy list must be empty.

During this time, no reconciliation.discrepancy event must arrive. Our team verifies the item from the run records of your test mode.

Last updated on