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

> ## Agent Instructions
> Bitnob API requests authenticate with an `X-Auth-Client` API key header against https://api.bitnob.com.
> Always use the sandbox environment for testing. Never put live API keys in sample code.
> Money amounts are integers in the smallest unit of the currency (satoshis for BTC, cents for fiat). Never use floats.
> Every state-changing request takes a client-supplied `reference` and must be treated as idempotent on retry.
> Quotes expire. Re-quote rather than reusing a stale quote id.

# Require approval for high-value transfers

> Hold transfers above a threshold until approvers sign off, with tiered approval counts and a fast path for small, routine payouts.

A `require_approval` rule holds a matching transaction in `pending_authorization` until enough approvers approve it. The transaction is persisted and its funds are locked while it waits. Once approved, it signs and broadcasts without being resubmitted.

This guide builds a tiered approval policy for a treasury team:

| Transfer value | Outcome |
| - | - |
| Over \$250,000 | Blocked |
| Over \$50,000 | Two approvals from the `admin` role |
| Over \$10,000 | One approval from the `treasury_officer` role |
| Automated sweeps between your own vaults, any value | Allowed |
| Everything else | No match |

## Choose your approvers

[List approver candidates](https://www.bitnob.dev/api-reference/platform/users/list-approver-candidates.md) returns every user and role you can name. Each entry's `id` and `type` go into the rule's `approvers` list.

* **Roles** (`"type": "role"`) cover everyone who holds the role at the time a transaction is held. `member_count` shows how many people that is now. Prefer roles.
* **Users** (`"type": "user"`) name one person. Use them only when a specific person must sign off, and plan for that person being away.

> [!WARNING]
>
> A rule that needs more approvals than its approvers can give holds every matching transaction until it expires. Keep `required_approvals` below the number of people who can approve, so one absence doesn't stop your payouts.

## Write the rules

Rules run in order and the first match wins, so order them from most to least restrictive. The sweep rule comes first so automated internal moves never wait for a person.

```json theme={null}
{
  "name": "Treasury approvals",
  "category": "approvals",
  "rules": [
    {
      "name": "Allow automated internal sweeps",
      "order": 1,
      "action_type": "allow",
      "conditions": [
        { "field": "is_automated", "operator": "equal", "value": "true" },
        { "field": "destination_type", "operator": "equal", "value": "vault" }
      ]
    },
    {
      "name": "Block over $250,000",
      "order": 2,
      "action_type": "block",
      "conditions": [
        { "field": "amount_usd", "operator": "greater_than", "value": "250000" }
      ]
    },
    {
      "name": "Two admins over $50,000",
      "order": 3,
      "action_type": "require_approval",
      "conditions": [
        { "field": "amount_usd", "operator": "greater_than", "value": "50000" }
      ],
      "action_config": {
        "required_approvals": 2,
        "approvers": [{ "id": "admin", "type": "role" }]
      }
    },
    {
      "name": "One treasury officer over $10,000",
      "order": 4,
      "action_type": "require_approval",
      "conditions": [
        { "field": "amount_usd", "operator": "greater_than", "value": "10000" }
      ],
      "action_config": {
        "required_approvals": 1,
        "approvers": [{ "id": "treasury_officer", "type": "role" }]
      }
    }
  ]
}
```

> [!NOTE]
>
> `approvers` and `required_approvals` are both required. Saving the draft rejects a rule without them, or with an unknown key. The values `destination_type` takes aren't listed in the API reference: dry-run a real transfer between your own vaults and check the value in `failed_condition.actual`.

Lint the rules with [Validate in-flight rule array](https://www.bitnob.dev/api-reference/platform/policies/validate-in-flight-rule-array.md), then save them with [Create draft policy](https://www.bitnob.dev/api-reference/platform/policies/create-draft-policy.md). [Write and activate your first policy](https://www.bitnob.dev/docs/platform/policies/write-and-activate-your-first-policy.md) shows both calls.

## Test each tier

Dry-run one request per row of the table, against the draft. Each should stop at the rule you expect.

| Request | Expected `trace` terminal rule | Expected decision |
| - | - | - |
| `"amount_usd": "300000"` | Block over \$250,000 | `result: rejected` |
| `"amount_usd": "75000"` | Two admins over \$50,000 | `result: awaiting_approval`, `required_approvals: 2` |
| `"amount_usd": "20000"` | One treasury officer over \$10,000 | `result: awaiting_approval`, `required_approvals: 1` |
| `"amount_usd": "300000", "is_automated": true, "destination_type": "vault"` | Allow automated internal sweeps | `result: approved` |
| `"amount_usd": "500"` | None | `result: approved` |

The fourth row is worth checking closely: the sweep rule is above the block rule, so it lets a \$300,000 automated sweep through. If that isn't what you want, add an `amount_usd` cap to the sweep rule.

When every row matches, validate the stored draft and activate it.

## What approvers see

When a transfer is held, the dry-run fields `required_approvals` and `expiry_hours` describe the approval request it opens. Approvals expire after 24 hours by default. Approvers act on the request in the Bitnob dashboard. The transaction then moves on:

* **Approved**: it continues to signing and broadcast, and you receive `transaction.signed` and then `transaction.completed` as normal.
* **Rejected**: its status becomes `rejected`, which is terminal. Nothing is signed or broadcast.
* **Expired**: the request lapses without enough approvals, and the transaction does not execute.

[Handle held and rejected transactions](https://www.bitnob.dev/docs/platform/policies/handle-held-and-rejected-transactions.md) covers how to show each state to your users.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.