> ## 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 overdraft with automatic split operations, repayment priority, and real-time tracking — supporting use cases from credit lines to BNPL and settlement accounts.

Balance Overdraft allows a balance to be debited beyond its available funds **without letting the primary balance go negative**. The deficit is tracked as **OverdraftUsed**, and Midaz automatically handles the operation split between the primary balance and an internal companion balance. When credits arrive, repayment is prioritized — the overdraft is repaid first, and any remainder flows to Available.

This mechanism supports credit lines, BNPL, settlement accounts, earned wage access, and any scenario where controlled negative positions are required.

## Balance direction

***

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

| Direction          | Behavior                          | Typical use                                                   |
| ------------------ | --------------------------------- | ------------------------------------------------------------- |
| `credit` (default) | Debit decreases, credit increases | Asset-like balances — checking accounts, wallets, reserves    |
| `debit`            | Debit increases, credit decreases | Liability-like balances — loans, overdraft tracking, payables |

<Note>
  Direction is set at creation time and 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>
  A `balanceScope` field also exists inside `settings` but is **system-managed** — it distinguishes internal balances (like the overdraft companion) from transactional balances. You do not set this field directly.
</Note>

## Configuration modes

***

### No overdraft (default)

The standard behavior. Any debit that exceeds the available balance is rejected.

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

### Unlimited overdraft

The balance can go negative without a cap. Useful for settlement or pool accounts where negative positions are normal and reconciled externally.

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

### Limited overdraft

The balance can go negative up to a defined limit. 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`, `overdraftLimit` is required and must be a positive decimal string. Omitting it or setting it to `"0"` 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, flooring it at **0**.
2. The excess is accrued as **OverdraftUsed** on the primary balance.
3. A companion operation is created on the internal `"overdraft"` balance (described below), recording the liability as a double-entry debit.

**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 a limit is configured, Midaz validates that the resulting OverdraftUsed does not exceed `overdraftLimit` **before** processing. If it would, the transaction is rejected with error `0167 - ErrOverdraftLimitExceeded`.
</Note>

### Automatic repayment (refund split)

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

1. The credit is applied to **OverdraftUsed first**, reducing the debt.
2. Any remaining amount after OverdraftUsed reaches 0 flows to **Available**.
3. A companion operation on the `"overdraft"` balance records the repayment.

**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 and cannot be bypassed. This ensures overdraft positions are always reduced as early as possible, keeping the balance healthy.
</Tip>

### Cancelling a pending overdraft transaction

When a `PENDING` transaction that drew overdraft is cancelled, Midaz keeps the companion balance in lockstep with the primary:

1. The cancel reverses the original hold and any overdraft drawn during the pending window — `OverdraftUsed` returns to its pre-pending value.
2. A companion `CREDIT` operation on the `"overdraft"` balance shrinks the liability by the exact amount that was drawn, repaying it.
3. Both the primary cancel and the companion credit are applied in the **same atomic batch**, so the primary balance and companion balance never drift out of sync — even if the cancel arrives in a high-throughput burst.

The engine calls this in-lockstep guarantee **companion parity**. It applies symmetrically across the hold, commit, and cancel phases of any pending transaction that touches overdraft.

## Position

***

Every balance response includes a computed `position` block that provides a real-time view of the balance state:

| Field                     | Description                                                                                                                          |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `available`               | `Balance.Available` minus `OverdraftUsed`. Can be negative when overdraft is active.                                                 |
| `onHold`                  | Mirrors `Balance.OnHold` — funds reserved by pending operations.                                                                     |
| `overdraftLimitAvailable` | Remaining overdraft headroom. `"0"` when overdraft is disabled. Omitted when unlimited. Positive decimal when a limit is configured. |

<Warning>
  The `position` block is **never persisted** — it is always computed at query time from the current balance state. Do not cache it for accounting purposes.
</Warning>

## Companion balance

***

The first time `allowOverdraft` flips to `true` on a balance — at creation or via update — Midaz auto-provisions a **companion balance** under the same account to record the liability side of the double-entry. It is created once per account and reused 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**:

* It cannot be created, modified, or deleted via the public API.
* The key `"overdraft"` is **reserved** — attempting to create a balance with this key returns error `0170 - ErrReservedBalanceKey`.
* It mirrors the liability as a proper double-entry record, ensuring the ledger stays balanced.

<Note>
  The `scope: "internal"` value gates direct user operations regardless of the permission flags above — any attempt to operate on this balance directly is rejected with error `0168 - ErrDirectOperationOnInternalBalance`. The companion only moves through system-driven overdraft enrichment.
</Note>

## Overdraft state on operations

***

Every operation exposes the overdraft state directly on the `balance` and `balanceAfter` blocks. The `overdraftUsed` field captures how much overdraft was consumed before and after the operation — providing a complete audit trail without needing a separate balance query.

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

System-managed companion operations on the `"overdraft"` balance are exposed with `type: "OVERDRAFT"` (uppercase). The `direction` field carries the lifecycle semantics: `"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, making the lifecycle visible from either row. The internal `snapshot` JSONB column on the `operations` table stores the same values for indexing and historical reconstruction; it is not part of the public JSON wire and surfaces on `balance.overdraftUsed` and `balanceAfter.overdraftUsed` instead. Future system-generated context can be added to the snapshot without breaking the public contract.

<Tip>
  Companion operations inherit `routeId`, `routeCode`, and `routeDescription` from the primary operation that triggered them. This keeps both legs of the double-entry under the same accounting rubric — chart-of-accounts classifications flow from the primary to the companion automatically, so reports filtered by route show both sides of the overdraft.
</Tip>

## Overdraft events

***

Midaz publishes lifecycle events to RabbitMQ when overdraft state changes. Publication is **enabled by default** — set the flag to `"false"` to opt out.

<CodeGroup>
  ```bash Environment theme={null}
  # Disable overdraft event publication (default: enabled).
  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",
      "overdraftLimit": "5000.00",
      "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 customer's checking balance can go negative up to a pre-approved limit, with interest accrued on the outstanding amount.

<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, creating an immediate overdraft position that is repaid 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. The overdraft position is cleared when payroll credits 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. The overdraft is repaid 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 the position is reconciled 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 earning them. The overdraft tracks the point deficit, repaid as new points are accrued.

<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 set at creation, a balance's `direction` cannot be changed.
* **Internal balances are read-only.** The `"overdraft"` companion balance cannot be created, deleted, or updated via the public API. PATCH requests on internal balances are rejected with error `0175`.
* **Reserved keys.** The key `"overdraft"` is reserved for the system-managed companion balance.
* **Disabling overdraft preserves outstanding debt.** Setting `allowOverdraft: false` while `OverdraftUsed > 0` is allowed — the flag blocks future overdraft draws, while incoming credits keep repaying the existing debt until it reaches zero.
* **Limit cannot drop below usage.** If `OverdraftUsed = 200`, setting `overdraftLimit: "100"` is rejected (error `0173`) — repay below the new ceiling first, or set a higher limit.
* **Optimistic concurrency.** Balance updates use version-based concurrency control. Stale writes are rejected 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.
