> For the complete documentation index, see [llms.txt](https://developers.bead.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.bead.xyz/settlement/settlement-reconciliation-patterns.md).

# Settlement Reconciliation Patterns

## Settlement Reconciliation Patterns

This page explains how to reconcile settlement activity across a full merchant portfolio efficiently, as a scheduled job rather than a loop that runs once per merchant. It also covers the timestamp, status field, and pagination details that most often trip up reconciliation jobs.

Use [Settlement data and deposit reconciliation](/faqs-and-troubleshooting/settlement-faqs/settlement-data-and-deposit-reconciliation.md) if you need the field level reference for tracing one deposit back to one settlement. Use this page if you're building a recurring job that reconciles many merchants at once.

**When to use this page**

Use these patterns when you need to:

* Run a nightly or scheduled reconciliation job across many merchants under a partner.
* Detect settlement status changes incrementally, without pulling everything again each cycle.
* Decide which status or amount field is authoritative when more than one looks relevant.
* Understand why a payment record doesn't reflect a settlement event you know happened.
* Avoid unnecessary per merchant API calls in a reconciliation loop.

**The pattern: pull once for the portfolio, not once per merchant**

[GET /Settlements](/settlement/settlements.md) accepts a `MerchantIds` array. If you serve multiple merchants under a partner, pass all of their merchant IDs in a single call rather than looping per merchant:

```
GET /Settlements?From=2026-07-15&To=2026-07-16&MerchantIds=mer_1&MerchantIds=mer_2&MerchantIds=mer_3
```

This returns settlement records for every merchant in the list in one paginated response. The same bulk filtering approach applies to [Batches](/settlement/batches.md), which accepts both `MerchantIds` and `SettlementIds` arrays. Building your reconciliation job around list calls with array filters, rather than one call per merchant per entity, is the single biggest efficiency gain available today.

> **Use the collection endpoints for this, not the path-scoped ones.** `MerchantIds` only functions on the collection endpoints `GET /Settlements` and `GET /Batches`. On `GET /Merchants/{merchantId}/settlements` it is accepted and returns HTTP 200 but has no effect: the path parameter is the only merchant scope, and passing a different merchant's ID returns the same path-scoped records. Confirmed by direct testing. Do not attempt a multi-merchant pull through a merchant-scoped path.

> **Date format on Settlement endpoints.** `From` and `To` on `GET /Settlements` and `GET /Batches` are date values (`2026-07-15`). A full ISO timestamp with a time component fails validation, confirmed by direct testing on both endpoints. This differs from the Reporting payment endpoints, where `From` and `To` are date-time values and accept either form.

> **`To` appears to be exclusive.** A same-day window (`From` and `To` set to the same date) returns zero records, while extending `To` by one day returns the expected record. Extend `To` past the last day you want included, and account for this when you advance a sync window so you do not skip a day.

**Detecting settlement changes incrementally**

Don't use a payment's `updated` timestamp to detect settlement events. Assigning a settlement to a payment does not change the payment record. Payments and settlements are separate entities, and this is expected behavior, not a bug. A payment can leave your sync window (if you're windowing on payment `updated`) before it's ever assigned to a settlement, and you'll never see that assignment on the payment side.

Detect changes on the settlement record instead. Two facts about the settlement record's timestamps determine how you do this correctly:

* `GET /Settlements` returns both `created` and `updated` on every settlement record, and both are orderable (`SortBy=created`, `SortBy=updated`). `updated` is the field to compare against your last stored value when deciding whether a settlement changed.
* **`From` and `To` filter on the settlement's `created` date, not `updated`.** They return settlements *created* in the window. `updated` is sortable but is not range filterable: there is no `updatedFrom` / `updatedTo` or `ModifiedSince` parameter on `GET /Settlements`.

Because the change signal (`updated`) and the filter axis (`created`) are different fields, **`From`/`To` on its own is not an updated-since feed.** A settlement created before your window that changes state inside it (for example `cleared` to `paid` several days after it was created, which is exactly the change you're polling for) is excluded, because it was filtered out on `created`. Don't window on `From`/`To` alone and assume you have caught every change.

Use this two part pattern instead:

1. **Discovery of new settlements.** Call `GET /Settlements` with your `MerchantIds` and a `From`/`To` window covering the period since your last sync, adding `SortBy=updated&SortDirection=desc`. This surfaces settlements *created* since you last ran, most recently changed first.
2. **Correctness for slow state changes.** Maintain a working set of the settlement IDs you have seen that are not yet in a terminal state (`status` not `paid`). Each run, re-fetch [GET /Settlements/{id}](/settlement/settlements.md) for those tracked IDs and compare `updated` and `status` against what you last stored. Drop an ID from the set once it reaches `paid`. Reading a settlement by ID is not affected by the `created` window, so this catches a transition no matter how long after creation it happens.

Compare both `updated` and `status` in step 2 rather than relying on `updated` alone. Treating a changed `status` as authoritative means your job still behaves correctly even if a given transition doesn't move the timestamp.

This bounds your work to *new settlements plus the settlements you are still tracking*, independent of total history, and does not miss a slow transition such as a settlement sitting in `hold` for several days before it clears.

If you want the simplest possible version, widen `From` to cover the full period in which a settlement can still change state and re-fetch that whole window each run, deduping against your last sync on your side. This is fine for normal timing, but a settlement can remain in `hold` longer than any fixed window, so pair the widened window with the ID tracking in step 2 for anything not yet `paid`.

**Two status fields that are not the same thing**

Payment history responses do **not** carry a payment level settlement processing status. The two status fields you will actually encounter live on different entities and describe different state machines:

| Field         | Lives on                                                            | Values                                  | What it actually means                                                                                                    |
| ------------- | ------------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `batchStatus` | Payment (Reporting APIs), and the batch record's own `status` field | `open`, `closed`, `hold`                | Where the **batch** containing this payment is in the batching process. Null on the payment until the payment is batched. |
| `status`      | Settlement record                                                   | `cleared`, `hold`, `dispatched`, `paid` | Where the **settlement** itself is in the funding and payout lifecycle.                                                   |

Both enums contain a value named `hold`, and they do not mean the same thing. A `batchStatus` of `hold` is a batch pending review. A settlement `status` of `hold` is the settlement itself held before funding, and in that case the settlement record's `holdReason` explains why (it is null unless `status` is `hold`).

These are sequential, not parallel: a payment is batched first, and the batch is later tied to a settlement that funds. So a payment can have `batchStatus` of `closed` while its settlement is still `cleared` rather than `paid`. That is not a contradiction, it just means the batch finished closing before the settlement finished funding. Don't infer funding or payout state from `batchStatus`, and don't infer batch state from settlement `status`.

> **There is no `paymentSettlementStatusCode` field.** Earlier revisions of this page described a `paymentSettlementStatusCode` field on the Reporting payment objects, with its own status enum. That field does not exist in the payment response schema on [Merchant](/reporting/merchant-payments.md), [Partner](/reporting/partner-payments.md), or [Terminal](/reporting/terminal-payments.md) Payments, and it is not available as a filter. Use the settlement fields described below instead.

**Settlement fields on a payment, by scope**

The settlement fields available on a payment differ slightly depending on which Reporting endpoint returned it:

| Endpoint                                                                            | `settlementId` | `batchId` | `batchStatus` | `batchPeriod` |
| ----------------------------------------------------------------------------------- | -------------- | --------- | ------------- | ------------- |
| [Merchant Payments](/reporting/merchant-payments.md) `GET /Merchants/{id}/payments` | Yes            | Yes       | Yes           | Yes           |
| [Partner Payments](/reporting/partner-payments.md) `GET /Partners/{id}/payments`    | Yes            | Yes       | Yes           | Yes           |
| [Terminal Payments](/reporting/terminal-payments.md) `GET /Terminals/{id}/payments` | Yes            | Yes       | Yes           | No            |

All of these are null until the payment is batched. If your reconciliation job needs `batchPeriod`, read the payment from Merchant or Partner scope rather than Terminal scope. See [Payment Settlement Detail](/settlement/payment-settlement-detail.md) for the state progression these fields move through.

**Settled amount: which field is authoritative**

Use the settlement record's `netFundableAmount` as the settled amount for a merchant or period. Don't use the payment's `settleableAmount` as your reconciled total. It reflects a single payment's settleable amount at the payment level, not the actual net amount funded after batch level fees, deposit fees, and adjustments (chargebacks, holdbacks, and so on) are applied. The settlement record is the only place those are all rolled up together, alongside `grossFundableAmount`, `totalTransactionFees`, `depositFees`, `totalFees`, `adjustments`, and `netAdjustmentTotal`. See [Settlements](/settlement/settlements.md) for the full net fundable amount formula.

**Known limitation: you can't filter the payments list by settlement or batch**

The merchant, partner, and terminal payments endpoints do not support filtering by `settlementId` or `batchId` directly. If you need the payments behind a specific settlement or batch, go the other direction:

* Read `batchIds` off the settlement record, or call [GET /Settlements/{id}/batches](/settlement/batches.md) to get the batch records themselves.
* Read `paymentIds` off each batch.
* Cross reference those IDs against your own payment records, or look them up individually.

Reading `batchIds` from the settlement you already have avoids an extra call when all you need are the batch identifiers. Call `GET /Settlements/{id}/batches` when you need the batch records' own fields, such as `status`, `batchPeriod`, `transactionCount`, `totalFees`, or `paymentIds`.

Don't build a reconciliation loop that assumes you can query payments by `settlementId`. That filter doesn't exist today.

**Pagination: zero based across the board**

Pagination is zero based across every paged endpoint referenced on this page. `Page=0` returns the first page in the Reporting family ([Partner](/reporting/partner-payments.md), [Merchant](/reporting/merchant-payments.md), and [Terminal](/reporting/terminal-payments.md) Payments) and in the Settlement family ([Settlements](/settlement/settlements.md), [Batches](/settlement/batches.md)). There is one shared convention, not two. `PageSize` defaults to `50` and its maximum is `100` on all of them.

If you previously read guidance here suggesting the Reporting family defaults to `Page` equals `1`, that was inaccurate and has been corrected.

**Typical integration flow: nightly portfolio reconciliation**

Goal: reconcile settlement activity for every merchant under a partner, once per night, without per merchant calls.

1. Track your last successful sync timestamp, and keep a working set of settlement IDs you have seen that are not yet `paid`.
2. Discovery call: `GET /Settlements` with `MerchantIds` set to your full merchant list and `From`/`To` covering the window since last sync, `SortBy=updated&SortDirection=desc`. Remember `From`/`To` filter on `created`, so this finds newly *created* settlements. Add any new non-terminal settlement IDs to your working set.
3. Change detection: re-fetch [GET /Settlements/{id}](/settlement/settlements.md) for each ID in your working set and treat a changed `updated` or `status` as a change to process. This catches transitions on settlements created before the current window. Drop an ID from the set once its `status` is `paid`.
4. If a settlement is in `hold`, read `holdReason` to understand why before escalating.
5. If you need batch level detail for a changed settlement, use its `batchIds` or call [GET /Settlements/{id}/batches](/settlement/batches.md).
6. If you need transaction level detail, use `paymentIds` from each batch to cross reference your own records.
7. Use `netFundableAmount` from the settlement record as your reconciled total for that merchant or period.
8. Advance your last sync timestamp only after the run completes successfully.

**Next steps**

* Use [Settlements](/settlement/settlements.md) for the full settlement field reference and the net fundable amount formula.
* Use [Batches](/settlement/batches.md) to review grouped payment activity within a settlement.
* Use [Payment Settlement Detail](/settlement/payment-settlement-detail.md) for the payment level settlement fields and their state progression.
* Use [Reporting](/reporting.md) and [Pagination and Sorting](/reporting/payment-history-concepts/pagination-and-sorting.md) for payment history and the Reporting family's pagination contract.
* Use [Settlement data and deposit reconciliation](/faqs-and-troubleshooting/settlement-faqs/settlement-data-and-deposit-reconciliation.md) for the single deposit reconciliation walkthrough.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers.bead.xyz/settlement/settlement-reconciliation-patterns.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
