# Phase 1: Balance Checking (Direct API)

Beta
Smart Collections is currently in Beta. Some contracts on this page are still being confirmed with our engineering team, so treat them as provisional and expect refinements before general availability, including:

- Webhook payloads and event codes
- API reference updates for the Collection Instructions, Payment Authorization, and Charge endpoints
- Field names and documentation improvements (terminology, diagrams)


Contact your Belvo account manager to join the Beta program.

In Phase 1 of Smart Collections, you delegate balance monitoring to Belvo. You define **Collection Instructions**, setting out the amounts you want to collect and the balance conditions that must be met, and Belvo watches the linked account in the background. When an account has enough money, Belvo notifies you so you can collect it in Phase 2.

Aggregation Product
Balance checking is part of Belvo's **aggregation** product: it relies on the OFDA consent and banking data from the user's Link.
As such, you must complete the Smart Collections prerequisites first, as you need an OFDA Link (`link_id`) to create Collection Instructions and a webhook endpoint to receive balance events.

Phase 1 covers three steps: you create a Collection Instruction, Belvo monitors the account balance against it, and Belvo sends you a webhook once there are enough funds to collect.


```mermaid
sequenceDiagram
    participant Client as Your application
    participant Aggregation as Belvo Aggregation API

    Note over Client, Aggregation: 1. Create the Collection Instruction

    Client->>Aggregation: POST /api/collection-instructions/
    Aggregation-->>Client: 201 Created (instruction and item IDs)

    Note over Client, Aggregation: 2. Belvo monitors the balance

    loop Within your collection windows, until the balance is sufficient
        Aggregation->>Aggregation: Check whether the account balance covers the amount
    end

    Note over Client, Aggregation: 3. Belvo signals that funds are available

    Aggregation->>Client: Webhook: item_ready_to_charge
    Note over Client, Aggregation: Carries the item_id, your external_id, and the amount.<br/>Continue in Phase 2 to create the Charge.
```

## 1. Create a Collection Instruction

A **Collection Instruction** indicates how much you want to collect from your user (`link_id`) and over what period of time. Belvo uses this information to monitor the link's account balance and once the balance condition is met, you will receive a webhook event indicating that you can pull funds for a given amount.

Each Collection Instruction item in the `items` array refers to a singular collection that you want to act on. **Most integrations create one Collection Instruction per debt**: a single item describing that debt, with one or more collection policies. (An instruction can hold multiple items if you want to batch several debts under one call, but one-item-per-debt is the common pattern.)

To create one, make a POST Create a collection instruction request:


```json Create a Collection Instruction Request Body
{
  "link_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "items": [
    {
      "amount": "150.50",
      "external_id": "inv-2026-001",
      "collection_policies": [
        {
          "start_date": "2026-11-01",
          "end_date": "2026-11-10",
          "amount": "150.50"
        },
        {
          "start_date": "2026-11-21",
          "end_date": "2026-11-30",
          "amount": "150.50"
        }
      ]
    }
  ]
}
```

What a collection policy does
A collection policy defines a **window of time** during which Belvo checks the account. Add more than one policy to an item to control *when* monitoring happens. In the example above, Belvo checks during the first ten days of November, pauses, then checks again in the last ten days. **No checks happen during the gap** between windows.

Both `start_date` and `end_date` are **inclusive**, so a window runs to the very end of its `end_date`.

Belvo notifies you as soon as the available balance of **any one** of the user's accounts is greater than the item's `amount`, and then stops monitoring that item. Balances across accounts aren't added together.

Windows are evaluated in UTC
Collection windows run from 00:00 UTC on `start_date` to 23:59:59 UTC on `end_date`, not in the payer's local time. In Brasília time, a window opens at 21:00 on the day before `start_date` and closes at 20:59 on `end_date`.

Within a window, Belvo checks the balance about seven times a day, at varying times. A balance that is only briefly high enough may be missed.

The available balance includes overdraft
Belvo uses the account's available balance as reported by the bank, which can include an overdraft limit (*cheque especial*). A user can therefore trigger `item_ready_to_charge` with less money of their own than the item's `amount`.

| Parameter | Required | Description |
|  --- | --- | --- |
| `link_id` | true | The `id` of the OFDA Link for the user whose account balance you want to monitor. It must be an active Link that belongs to your organization, otherwise Belvo responds with `400 - Bad Request`. |
| `items` | true | Array of items, with at least one entry. Each item is one amount you want to collect, with one or more collection policies. Most Collection Instructions have exactly **one** item, one debt per instruction. |
| `items[].amount` | true | The amount you want to collect for this item, as a string. Belvo notifies you once the available balance of one of the user's accounts is greater than this amount. |
| `items[].external_id` | optional (strongly recommended) | Your own reference for this item. Belvo does not link Collection Instructions to the Payment Authorization or Charge you create in Phase 2, so this is how you tie a webhook back to your records. Set it unless you have a reason not to. Since most instructions hold one item per debt, this is typically your reference for the debt as a whole. |
| `items[].collection_policies` | true | Array of policies, with at least one entry. Each policy defines a window of time (`start_date` to `end_date`) during which Belvo checks the account. Use several policies to control when monitoring happens. |
| `items[].collection_policies[].start_date` | true | The first date on which Belvo checks the account, in `YYYY-MM-DD` format. Inclusive. |
| `items[].collection_policies[].end_date` | true | The last date on which Belvo checks the account, in `YYYY-MM-DD` format. Evaluated in UTC, see the note below. |
| `items[].collection_policies[].amount` | true | The amount for this window, as a string. Set it to the amount you want to collect for the item. See the note below. |


Set each policy's amount to the item's amount
Every policy requires an `amount`, but today Belvo compares the balance with the **item's** `amount` in every window, and a policy's `amount` has no effect. Set each policy's `amount` to the same value as the item's `amount`.


```json Create a Collection Instruction Response (201 Created)
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", // [!code highlight]
  "link_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "PROCESSING",
  "created_at": "2026-10-25T14:22:10.123456Z",
  "updated_at": "2026-10-25T14:22:10.123456Z",
  "items": [
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", // [!code highlight]
      "amount": "150.50",
      "external_id": "inv-2026-001",
      "status": "PENDING",
      "created_at": "2026-10-25T14:22:10.234567Z",
      "updated_at": "2026-10-25T14:22:10.234567Z",
      "collection_policies": [
        {
          "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
          "start_date": "2026-11-01",
          "end_date": "2026-11-10",
          "amount": "150.50",
          "created_at": "2026-10-25T14:22:10.345678Z"
        },
        {
          "id": "d4e5f6a7-b8c9-0123-defa-234567890123",
          "start_date": "2026-11-21",
          "end_date": "2026-11-30",
          "amount": "150.50",
          "created_at": "2026-10-25T14:22:10.345678Z"
        }
      ]
    }
  ]
}
```

The instruction starts in `PROCESSING` and each item in `PENDING`. See Statuses and webhooks for the full lifecycle.

Save the instruction and item IDs
Store the instruction `id` and each item `id` from this response. You need the instruction `id` to check or cancel the instruction, and you need both IDs to cancel a single item. No endpoint resolves an item `id` back to its parent instruction. If you lose an instruction `id`, you can find it again by listing your instructions.

## 2. Listen for the `item_ready_to_charge` webhook

When the balance condition is met, Belvo sends a single webhook and stops monitoring that item. The item moves to `CHARGE_SETTLED`, which only means the webhook was sent: no Charge exists yet.


```json item_ready_to_charge webhook
{
  "webhook_id": "e29b41d4a7164a4fae1112b8c9d3f5a2",
  "webhook_type": "COLLECTIONS",
  "webhook_code": "item_ready_to_charge",
  "process_type": null,
  "link_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "external_id": null,
  "data": {
    "item_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "external_id": "inv-2026-001",
    "amount": "150.50"
  }
}
```

The payload identifies the matched item (`data.item_id`) and its `external_id`, along with the `amount` to collect. This is an **aggregation** webhook, so it has no V1/V2 versioning. For a full field-by-field breakdown, see Statuses and webhooks.

Use external_id to bridge the phases
Belvo doesn't link a Collection Instruction to the Payment Authorization or Charge you create next, so use `data.external_id` (or `data.item_id`) to correlate this webhook with the Charge you trigger in Phase 2.

When you receive `item_ready_to_charge`, proceed to Phase 2 and charge `data.amount` against the user's Payment Authorization.

No webhook is sent if the balance is never sufficient
If none of an item's windows find enough balance by their `end_date`, the item moves to `POLICY_EXPIRED` **silently, with no webhook sent**. The same happens if Belvo can't read the user's accounts (`CHARGE_REJECTED`). Don't wait indefinitely for `item_ready_to_charge`: track your items' last `end_date` and reconcile any that never produced a Charge on your side.

## Check the status of your items

Belvo only sends a webhook when an item reaches `CHARGE_SETTLED`. To find out how any other item ended, or to recover if you missed a webhook, get the instruction's details and read each item's `status`:


```shell Get a Collection Instruction
curl --request GET 'https://api.belvo.com/api/collection-instructions/{instruction_id}/' \
  -u SECRET_ID:SECRET_PASSWORD
```

Belvo responds with `200 - OK` and the same object you received when you created the instruction, with the current `status` of each item. See Get a collection instruction's details. The instruction's own `status` stays `PROCESSING` until you cancel it, even after every item reaches a final status, so always read the items' statuses.

To find an instruction when you don't have its `id`, make a GET List collection instructions request:


```shell List Collection Instructions
curl --request GET 'https://api.belvo.com/api/collection-instructions/?created_after=2026-11-01&created_before=2026-12-01' \
  -u SECRET_ID:SECRET_PASSWORD
```

The response is an array of summaries (`id`, `link_id`, `status`, and timestamps) that **doesn't include the items**. Get the details of an instruction to see them.

| Query parameter  | Description |
|  --- | --- |
| `status` | Return only instructions with this status (`PROCESSING` or `CANCELLED`). |
| `created_after` | Return only instructions created on or after this date (`YYYY-MM-DD`), from 00:00 UTC. |
| `created_before` | Return only instructions created before this date (`YYYY-MM-DD`). The date itself is excluded. |


The `consent_id` filter is also available, see the API reference.

Keep list requests small and don't expect an order
The list isn't paginated and its order isn't guaranteed. Always send `created_after` and `created_before`, so that the response only covers the period you want to reconcile. A filter value that isn't valid, for example an unknown `status` or a date that isn't in `YYYY-MM-DD` format, returns a generic `500 - Internal Server Error`.

How often to check
Use these requests to reconcile, not as your main signal: the `item_ready_to_charge` webhook is how you learn that an item is ready. Belvo checks balances about seven times a day, so asking more often doesn't surface anything sooner. A good moment to check an item is after its last `end_date`, to find items that ended as `POLICY_EXPIRED`, `CHARGE_REJECTED`, or `FAILED`.

An `instruction_id` that doesn't exist or doesn't belong to your organization returns a generic `500 - Internal Server Error`. An `instruction_id` that isn't a UUID returns `404 - Not Found`.

## Retry after a stopped collection

Monitoring for an item stops permanently once Belvo sends `item_ready_to_charge`, or once its windows end without enough funds. A Collection Instruction does **not** resume monitoring on its own, including after a Charge you create later is rejected. To try to collect again, create a **new** Collection Instruction. Reusing the same `external_id` on the new item is fine.

## Mark an item as complete

If you collect an item's amount some other way before Belvo finds enough balance, for example because your user pays you directly, mark the item as complete. Belvo stops monitoring it, and the item's status becomes `COMPLETED`, so your records and Belvo's agree on why monitoring ended.

Make a PATCH Mark a collection instruction item as complete request, and identify the item by its `id` or by the `external_id` you gave it:

By item id

```shell Mark an item as complete
curl --request PATCH 'https://api.belvo.com/api/items/complete/' \
  -u SECRET_ID:SECRET_PASSWORD \
  -H 'Content-Type: application/json' \
  --data '{
    "item_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
  }'
```

By external_id

```shell Mark an item as complete
curl --request PATCH 'https://api.belvo.com/api/items/complete/' \
  -u SECRET_ID:SECRET_PASSWORD \
  -H 'Content-Type: application/json' \
  --data '{
    "external_id": "inv-2026-001"
  }'
```

| Parameter  | Required | Description |
|  --- | --- | --- |
| `item_id` | one of the two | The `id` of the item, from the Collection Instruction response. |
| `external_id` | one of the two | The `external_id` you set on the item. It must match exactly one item in your organization. |


Send exactly one of `item_id` or `external_id`. Belvo responds with `200 - OK` and the item:


```json Response: 200 - OK
{
  "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "amount": "150.50",
  "external_id": "inv-2026-001",
  "status": "COMPLETED", // [!code highlight]
  "created_at": "2026-10-25T14:22:10.234567Z",
  "updated_at": "2026-11-05T09:12:44.104311Z",
  "collection_policies": [
    {
      "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
      "start_date": "2026-11-01",
      "end_date": "2026-11-10",
      "amount": "150.50",
      "created_at": "2026-10-25T14:22:10.345678Z"
    },
    {
      "id": "d4e5f6a7-b8c9-0123-defa-234567890123",
      "start_date": "2026-11-21",
      "end_date": "2026-11-30",
      "amount": "150.50",
      "created_at": "2026-10-25T14:22:10.345678Z"
    }
  ]
}
```

Only PENDING items can be completed
You can only mark an item as complete while its status is `PENDING`. If a balance check is in progress (`PROCESSING`), try again a few moments later. Items in any other status can't be completed.

At the moment, every rejected request (an item that isn't `PENDING`, an unknown `item_id`, or an `external_id` shared by several of your items) returns a generic `500 - Internal Server Error`. Use the item `id` when you can, to avoid ambiguous matches.

The difference from cancelling is the reason you record: `COMPLETED` means the amount was collected outside Belvo, and `CANCELLED` means you no longer want to collect it.

## Cancel a Collection Instruction

To stop Belvo from monitoring because you no longer want to collect, for example if the debt was renegotiated or written off, cancel the whole instruction or a single item with a `PATCH`. (Cancellation is a `PATCH` rather than a `DELETE` so that Belvo retains an audit trail.)

| Action | Endpoint | Behavior |
|  --- | --- | --- |
| Cancel the whole instruction | `PATCH /api/collection-instructions/{instruction_id}/` | Cancels every item that is still `PENDING` or `PROCESSING`, and sets the instruction to `CANCELLED`. Returns the instruction. |
| Cancel a single item | `PATCH /api/collection-instructions/{instruction_id}/items/{item_id}/` | Only while the item is `PENDING` or `PROCESSING`. Returns the item, now `CANCELLED`. |


Neither request takes a body. See Cancel a collection instruction and Cancel a collection instruction item. Belvo responds with `200 - OK`.

Keep these points in mind:

- **Cancelling can't be undone.** To monitor again, create a new Collection Instruction.
- **Final statuses stay as they are.** Items that already reached a final status, such as `CHARGE_SETTLED` or `COMPLETED`, keep their status when you cancel their instruction.
- **Cancelling an item doesn't change its instruction.** The instruction stays `PROCESSING`, even if you cancelled its last active item.
- **Cancelling the same instruction twice is safe.** Belvo responds with `200 - OK` again.


Rejected requests return a generic 500
Belvo returns a generic `500 - Internal Server Error` when the instruction or item doesn't exist, doesn't belong to your organization, or isn't in a status that allows the cancellation (for an item, `PENDING` or `PROCESSING`). An item that doesn't belong to the instruction in the path is rejected the same way. An ID that isn't a UUID returns `404 - Not Found`. Check an item's status with Get a Collection Instruction before you cancel it.