> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Balance Overdraft

> Enable controlled Balance Overdraft in Midaz with automatic split operations, credit repayment priority, and limits for BNPL or settlement accounts.

Balance Overdraft lets you debit a balance beyond its available funds. The primary balance never goes negative. Midaz tracks the deficit as **OverdraftUsed** and splits the operation between the primary balance and an internal companion balance. When credits arrive, Midaz repays the overdraft first. Any remainder flows to Available.

This mechanism supports credit lines, BNPL, settlement accounts, earned wage access, and any product that needs controlled negative positions.

## Balance direction

***

Balances carry a `direction` field that defines how debits and credits affect the balance:

| Direction | Behavior                          | Typical use                          |
| --------- | --------------------------------- | ------------------------------------ |
| `credit`  | Debit decreases, credit increases | Checking accounts, wallets, reserves |
| `debit`   | Debit increases, credit decreases | Loans, overdraft tracking, payables  |

At creation, Midaz uses an explicit `direction` when provided, then the Account Type's `defaultDirection`. If neither is set, external Accounts use `debit`; all other Accounts use `credit`.

<Note>
  You set direction at creation time. It is **immutable**. The overdraft companion balance (described below) always uses `direction=debit`.
</Note>

## Balance settings

***

The `settings` object on a balance controls overdraft behavior:

| Field                   | Type             | Description                                                                                          |
| ----------------------- | ---------------- | ---------------------------------------------------------------------------------------------------- |
| `allowOverdraft`        | boolean          | Enables overdraft on this balance                                                                    |
| `overdraftLimitEnabled` | boolean          | Gates whether a limit is enforced                                                                    |
| `overdraftLimit`        | string (decimal) | Maximum overdraft amount. Required when `overdraftLimitEnabled` is `true`. Must be greater than `0`. |

<Note>
  The `settings` object also carries `balanceScope`. It identifies a transactional balance (the default) or a system-managed internal balance, such as the overdraft companion. You can set `balanceScope: "transactional"` when you create or update a public balance. You cannot set `balanceScope: "internal"` through the public API.
</Note>

## Configuration modes

***

### No overdraft (default)

The standard behavior. Midaz rejects any debit that exceeds the available balance.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "checking",
    "assetCode": "BRL",
    "settings": {
      "allowOverdraft": false
    }
  }
  ```
</CodeGroup>

### Unlimited overdraft

The derived position can go negative without a cap. The persisted `Available` balance remains at `0`, while Midaz tracks the deficit as `OverdraftUsed`. Use this for settlement or pool accounts, where negative positions are normal and you reconcile them externally.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "settlement",
    "assetCode": "USD",
    "settings": {
      "allowOverdraft": true,
      "overdraftLimitEnabled": false
    }
  }
  ```
</CodeGroup>

### Limited overdraft

The derived position can go negative up to a defined limit. The persisted `Available` balance remains at `0`, while Midaz tracks the deficit as `OverdraftUsed`. This is the most common mode for consumer credit products.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "checking",
    "assetCode": "BRL",
    "settings": {
      "allowOverdraft": true,
      "overdraftLimitEnabled": true,
      "overdraftLimit": "5000.00"
    }
  }
  ```
</CodeGroup>

<Warning>
  When `overdraftLimitEnabled` is `true`, you must set `overdraftLimit` to a positive decimal string. If you omit it or set it to `"0"`, Midaz returns error `0172 - ErrInvalidBalanceSettings`.
</Warning>

## How overdraft works

***

### Operation split

When a debit transaction exceeds the available funds, Midaz automatically splits the operation:

1. The debit consumes all remaining Available and floors it at **0**.
2. Midaz accrues the excess as **OverdraftUsed** on the primary balance.
3. If Midaz finds the internal `"overdraft"` balance (described below), it creates a companion operation. This operation records the liability as a double-entry debit. If it cannot find that balance, Midaz skips the companion operation; the primary balance still accrues **OverdraftUsed**.

**Example:** Balance has Available = 300. A debit of 500 arrives.

| Step   | Available | OverdraftUsed | Description                                           |
| ------ | --------- | ------------- | ----------------------------------------------------- |
| Before | 300       | 0             | Normal state                                          |
| After  | 0         | 200           | 300 consumed from Available, 200 accrued as overdraft |

The transaction succeeds as a single atomic operation. The caller does not need to handle the split — Midaz does it automatically.

<Note>
  If you configure a limit, Midaz checks the resulting OverdraftUsed against `overdraftLimit` **before** it processes the transaction. If the result exceeds the limit, Midaz rejects the transaction with error `0167 - ErrOverdraftLimitExceeded`.
</Note>

### Automatic repayment (refund split)

When a credit arrives and `OverdraftUsed > 0`, Midaz prioritizes repayment:

1. Midaz applies the credit to **OverdraftUsed** first and reduces the debt.
2. Any remaining amount after OverdraftUsed reaches 0 flows to **Available**.
3. If Midaz finds the internal `"overdraft"` balance, a companion operation on it records the repayment. If it cannot find that balance, Midaz skips the companion operation; the credit still repays **OverdraftUsed** on the primary balance.

**Example:** OverdraftUsed = 200, Available = 0. A credit of 350 arrives.

| Step   | Available | OverdraftUsed | Description                       |
| ------ | --------- | ------------- | --------------------------------- |
| Before | 0         | 200           | Overdraft active                  |
| After  | 150       | 0             | 200 repaid, 150 goes to Available |

<Tip>
  Repayment is automatic. You cannot bypass it. Midaz reduces overdraft positions as early as possible, which keeps the balance healthy.
</Tip>

### Cancelling a pending overdraft transaction

When you cancel a `PENDING` transaction that drew overdraft:

1. The cancel reverses the original hold and any overdraft drawn during the pending window. `OverdraftUsed` returns to its value before the hold.
2. If Midaz finds the internal `"overdraft"` balance, a companion `CREDIT` operation on it shrinks the liability by the exact amount drawn. If it cannot find that balance, Midaz skips the companion operation; the primary cancel still restores `OverdraftUsed`.
3. When Midaz creates the companion credit, it applies the primary cancel and the companion credit in the **same atomic batch**, so the two balances do not drift.

While the internal `"overdraft"` balance exists, Midaz keeps it in step with the primary balance across the hold, commit, and cancel phases of any pending transaction that touches overdraft.

## Position

***

Every balance response includes a computed `position` block. It gives a real-time view of the balance state:

| Field                     | Description                                                                                                                              |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `available`               | Derived `position.available`. It can be negative when the persisted balance has `Available = 0` and `OverdraftUsed > 0`.                 |
| `onHold`                  | Mirrors `Balance.OnHold` — funds reserved by pending operations.                                                                         |
| `overdraftLimitAvailable` | Remaining non-negative overdraft headroom. It is `"0"` when a configured limit is fully used and is omitted when overdraft is unlimited. |

<Warning>
  Do not cache the `position` block for accounting purposes. Midaz never persists it — it computes the block at query time from the current balance state.
</Warning>

## Companion balance

***

When you update a balance to set `allowOverdraft` to `true` for the first time, Midaz auto-provisions a **companion balance** under the same account. The companion balance records the liability side of the double-entry. Midaz creates it once per account and reuses it across every overdraft draw and repayment.

| Property         | Value         | Why                                                                    |
| ---------------- | ------------- | ---------------------------------------------------------------------- |
| `key`            | `"overdraft"` | Reserved system key                                                    |
| `direction`      | `debit`       | The companion tracks a liability — debits grow it, credits shrink it   |
| `scope`          | `internal`    | Blocks direct user operations                                          |
| `allowSending`   | `true`        | Required for DEBIT operations on the companion (overdraft draws)       |
| `allowReceiving` | `true`        | Required for CREDIT operations on the companion (overdraft repayments) |

This balance is **fully system-managed**:

* You cannot create, modify, or delete it through the public API.
* Midaz **reserves** the key `"overdraft"`. A request that creates a balance with this key returns error `0170 - ErrReservedBalanceKey`.
* It mirrors the liability as a proper double-entry record, so the ledger stays balanced.

<Note>
  The `scope: "internal"` value blocks direct user operations, regardless of the permission flags above. Midaz rejects any direct operation on this balance with error `0168 - ErrDirectOperationOnInternalBalance`. The companion moves only through system-driven overdraft enrichment.
</Note>

## Overdraft state on operations

***

Every operation exposes the overdraft state on the `balance` and `balanceAfter` blocks. The `overdraftUsed` field records the overdraft consumed before and after the operation. This gives a complete audit trail without a separate balance query.

For operations that do not touch overdraft, both values are `"0"`.

System-managed companion operations on the `"overdraft"` balance use `type: "OVERDRAFT"` (uppercase). The `direction` field carries the lifecycle: `"debit"` for a draw, `"credit"` for a repayment.

<CodeGroup>
  ```json Primary debit drawing overdraft theme={null}
  {
    "type": "DEBIT",
    "direction": "debit",
    "amount": { "value": "500" },
    "accountAlias": "@user123",
    "balanceKey": "checking",
    "balance": {
      "available": "300",
      "onHold": "0",
      "version": 1,
      "overdraftUsed": "0"
    },
    "balanceAfter": {
      "available": "0",
      "onHold": "0",
      "version": 2,
      "overdraftUsed": "200"
    }
  }
  ```

  ```json Companion overdraft draw theme={null}
  {
    "type": "OVERDRAFT",
    "direction": "debit",
    "amount": { "value": "200" },
    "balanceKey": "overdraft",
    "balance": {
      "available": "0",
      "onHold": "0",
      "version": 1,
      "overdraftUsed": "0"
    },
    "balanceAfter": {
      "available": "200",
      "onHold": "0",
      "version": 2,
      "overdraftUsed": "200"
    }
  }
  ```
</CodeGroup>

Both the primary and the companion operation share the same `overdraftUsed` before/after pair. They mirror the primary balance's overdraft transition, so the lifecycle is visible from either row. The internal `snapshot` JSONB column on the `operations` table stores the same values for indexing and historical reconstruction. This column is not part of the public JSON wire. The values surface on `balance.overdraftUsed` and `balanceAfter.overdraftUsed` instead. Midaz can add future system-generated context to the snapshot without breaking the public contract.

<Tip>
  Companion operations inherit the primary operation's `routeId`. For each overdraft-capable route, configure both the `debit` and `credit` rubrics of the `overdraft` entry; Midaz requires both. Midaz resolves `routeCode` and `routeDescription` from the rubric that matches the companion's direction.
</Tip>

## Overdraft events

***

At runtime, Midaz enables overdraft-event publication unless `RABBITMQ_OVERDRAFT_EVENTS_ENABLED` is explicitly `false`. The bundled example environment sets the flag to `false`; a deployment that starts from that example publishes no overdraft events until you set it to `true`.

<CodeGroup>
  ```bash Environment theme={null}
  # The bundled example disables overdraft-event publication. Runtime enables it unless the flag is explicitly false.
  RABBITMQ_OVERDRAFT_EVENTS_ENABLED=false

  # Optional: route overdraft events to a dedicated exchange.
  # When unset, the broker's default exchange is used.
  RABBITMQ_OVERDRAFT_EVENTS_EXCHANGE=transaction.overdraft_events.exchange
  ```
</CodeGroup>

### Event types

| Event               | Description                                                              |
| ------------------- | ------------------------------------------------------------------------ |
| `overdraft.drawn`   | Overdraft was consumed — OverdraftUsed increased                         |
| `overdraft.repaid`  | Overdraft was partially repaid — OverdraftUsed decreased but remains > 0 |
| `overdraft.cleared` | Overdraft was fully repaid — OverdraftUsed reached 0                     |

### Example event payload

<CodeGroup>
  ```json JSON expandable theme={null}
  {
    "source": "midaz",
    "eventType": "balance",
    "action": "overdraft.drawn",
    "timestamp": "2026-04-28T14:30:00.000000Z",
    "version": "v3.0.0",
    "organizationId": "0198575d-f9fd-702b-bb15-fa4c980b32c7",
    "ledgerId": "0198575d-fa0b-7ac7-8b7d-9d3ab7dccafc",
    "payload": {
      "accountId": "0198575f-a8f9-7924-a6d7-8122f2c77ddd",
      "transactionId": "019b2c3d-4e5f-6789-0123-456789abcdef",
      "amount": "200",
      "overdraftBalance": "200",
      "timestamp": "2026-04-28T14:30:00.000000Z"
    }
  }
  ```
</CodeGroup>

<Tip>
  Use overdraft events to trigger downstream workflows — interest accrual, customer notifications, risk alerts, or automatic collection processes.
</Tip>

## Use cases

***

### Checking account overdraft (cheque especial)

Classic consumer credit. The checking account's derived position can go negative up to a pre-approved limit; the persisted `Available` balance remains at `0` and the outstanding amount is tracked as `OverdraftUsed`.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "checking",
    "assetCode": "BRL",
    "settings": {
      "allowOverdraft": true,
      "overdraftLimitEnabled": true,
      "overdraftLimit": "2000.00"
    }
  }
  ```
</CodeGroup>

### Buy Now, Pay Later (BNPL)

A BNPL provider issues a purchase credit against the customer's balance. This creates an immediate overdraft position that the customer repays in installments.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "bnpl",
    "assetCode": "BRL",
    "settings": {
      "allowOverdraft": true,
      "overdraftLimitEnabled": true,
      "overdraftLimit": "10000.00"
    }
  }
  ```
</CodeGroup>

### Earned Wage Access / Salary advance

Employees draw against future earnings. Payroll credits clear the overdraft position when they arrive.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "salary-advance",
    "assetCode": "BRL",
    "settings": {
      "allowOverdraft": true,
      "overdraftLimitEnabled": true,
      "overdraftLimit": "3000.00"
    }
  }
  ```
</CodeGroup>

### Marketplace receivables advance

Sellers receive an advance on future receivables. Midaz repays the overdraft automatically as sales settlements arrive.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "receivables",
    "assetCode": "BRL",
    "settings": {
      "allowOverdraft": true,
      "overdraftLimitEnabled": true,
      "overdraftLimit": "50000.00"
    }
  }
  ```
</CodeGroup>

### Settlement / Pool accounts (unlimited mode)

Settlement and pool accounts routinely go negative during intraday processing. Unlimited overdraft avoids artificial rejections while you reconcile the position by end-of-day.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "settlement-pool",
    "assetCode": "USD",
    "settings": {
      "allowOverdraft": true,
      "overdraftLimitEnabled": false
    }
  }
  ```
</CodeGroup>

### Revolving credit lines (B2B)

Businesses draw and repay from a revolving credit facility. The overdraft limit represents the total credit line.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "credit-line",
    "assetCode": "BRL",
    "settings": {
      "allowOverdraft": true,
      "overdraftLimitEnabled": true,
      "overdraftLimit": "500000.00"
    }
  }
  ```
</CodeGroup>

### Insurance pre-financing

Insurers pre-finance claims before premium collection cycles close. The overdraft covers the gap between payout and collection.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "claims-prefin",
    "assetCode": "BRL",
    "settings": {
      "allowOverdraft": true,
      "overdraftLimitEnabled": true,
      "overdraftLimit": "100000.00"
    }
  }
  ```
</CodeGroup>

### Loyalty programs (advanced points)

Customers redeem points before they earn them. The overdraft tracks the point deficit and clears as customers accrue new points.

<CodeGroup>
  ```json JSON theme={null}
  {
    "key": "loyalty-points",
    "assetCode": "POINTS",
    "settings": {
      "allowOverdraft": true,
      "overdraftLimitEnabled": true,
      "overdraftLimit": "10000"
    }
  }
  ```
</CodeGroup>

## Protection rules

***

Overdraft introduces several immutability and access constraints to maintain ledger integrity:

* **Direction is immutable.** Once you set a balance's `direction` at creation, you cannot change it.
* **Internal balances block writes.** You cannot create, delete, or update the `"overdraft"` companion balance through the public API — a PATCH returns error `0175`.
* **Reserved keys.** Midaz reserves the key `"overdraft"` for the system-managed companion balance.
* **Disabling overdraft preserves outstanding debt.** You can set `allowOverdraft: false` while `OverdraftUsed > 0` to block future draws, while incoming credits still repay the existing debt.
* **Limit cannot drop below usage.** If `OverdraftUsed = 200`, Midaz rejects `overdraftLimit: "100"` with error `0173`, so repay below the new ceiling first or set a higher limit.
* **Optimistic concurrency.** Balance updates use version-based concurrency control, and Midaz rejects a stale write with error `0174` — retry with the latest version.

For the complete catalog of overdraft-related error codes (0167–0175), see the [Midaz error list](/en/reference/midaz/error-list).

## Next steps

***

* Learn about [Balances](/en/midaz/balances) — the foundation that overdraft builds on.
* Understand [Operations](/en/midaz/operations) to trace how overdraft splits appear in the ledger.
* Set up the [Event Publisher](/en/midaz/event-publisher) to consume overdraft lifecycle events.
* Explore [Transactions](/en/midaz/transactions) for the full picture of double-entry accounting in Midaz.
