> ## 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.

# How Bitnob Enterprise evaluates policy rules

> Rules run in order and the first full match wins. Learn how conditions, actions, and categories combine into a decision before signing.

Every signing request passes through the policy engine before the Bitnob Secure Enclave signs it. The engine compares the request against the rules in your organization's enforced policy and returns one decision. This page explains how it reaches that decision, so you can predict what a rule set will do before you write it.

## The objects

A **policy** is a named, ordered list of rules. Your organization has at most one policy in `enforced` status at a time. Any others are drafts or archived versions.

A **rule** has three parts:

* **Conditions**: a list of comparisons against the request, such as `amount_usd greater_than 10000`. Every condition in a rule must match for the rule to match.
* **An action**: what happens when the rule matches. One of `allow`, `block`, `require_approval`, `delay`, or `alert`.
* **An order**: the rule's position in the list. Lower numbers run first.

A **condition** has a `field`, an `operator`, and a value. The value goes in `value`, `values`, or `min` and `max`, depending on the operator. See [Condition values](https://www.bitnob.dev/docs/platform/policies/rule-reference.md#condition-values).

```json theme={null}
{
  "name": "Approve transfers over $10,000",
  "order": 1,
  "action_type": "require_approval",
  "conditions": [
    { "field": "amount_usd", "operator": "greater_than", "value": "10000" }
  ]
}
```

## First match wins

The engine walks the rules in `order`, from first to last. For each rule it checks the conditions in turn, and stops at the first condition that fails. The first rule whose conditions all match is the **terminal** rule: its action becomes the decision, and the rules after it are not evaluated.

Three consequences follow, and most policy bugs come from one of them:

* **Order is meaning.** A broad `allow` placed above a narrow `block` makes the `block` unreachable. Put the most specific and most restrictive rules first.
* **Conditions within a rule are AND.** To express "chain is Tron **or** chain is Ethereum", use one condition with the `in` operator, not two conditions.
* **Rules are OR.** To express "block if A, and separately block if B", write two rules.

[Validate stored draft policy](https://www.bitnob.dev/api-reference/platform/policies/validate-stored-draft-policy.md) catches the first problem for you: it warns about rules that are shadowed by an earlier rule, for example with the code `shadowed_by_different_action`.

## What the engine can see

A condition can compare any request attribute the engine evaluates. These include:

| Attribute | Examples of what it lets you express |
| - | - |
| `amount`, `amount_usd` | Thresholds in the asset's display unit, or in US dollars across assets |
| `asset_symbol`, `chain`, `network`, `token_address` | Restrict by asset or chain |
| `to_address`, `from_address`, `destination_type` | Allowlists and blocklists of destinations |
| `wallet_id`, `vault_id`, `source_type` | Different rules for treasury and customer wallets |
| `actor_type`, `actor_id`, `user_id` | Different rules for API keys and dashboard users, or for one specific key |
| `is_automated`, `automation_rule_id` | Different rules for automated sweeps and manual transfers |
| `operation_type`, `tx_type` | Different rules for each kind of operation |

[Rule reference](https://www.bitnob.dev/docs/platform/policies/rule-reference.md#condition-fields) describes each field. Saving a rule with a field the engine doesn't know returns `400 invalid_condition_field`.

> [!TIP]
>
> Prefer `amount_usd` for spending thresholds. A single USD rule covers every asset, and you don't need a separate rule per token with its own decimals.

## When no rule matches

If no rule matches, the request is allowed. A dry-run returns `"result": "approved"` and a trace in which every rule has `matched: false`. The same result comes back, with the reason "No active policy and no quorum", when your organization has nothing enforced.

So a policy that only lists what to block or hold allows everything else. If you want a default-deny posture, write the allow rules first and finish with a `block` rule that catches what's left.

> [!WARNING]
>
> If the engine can't work out a request's USD value, conditions on `amount_usd` don't match, and the trace reports a resolution error such as "rate provider unavailable". A `block` rule that relies only on `amount_usd` then lets the request through. For your most important limits, add a matching rule on `amount` for the assets you hold.

Don't rely on an implicit default for anything security-relevant. End your rule list with an explicit decision for the cases you care about, and confirm the behavior by dry-running a request that matches none of your other rules. See [Test rules with dry-run evaluation](https://www.bitnob.dev/docs/platform/policies/test-rules-with-dry-run-evaluation.md).

## Actions and the transaction status they produce

| Action | Decision | Transaction status | Create transaction returns |
| - | - | - | - |
| `allow` | Allowed | Moves on to `pending_signature` and `signed` | `201` |
| `alert` | Allowed, `policy.alert` sent | Moves on as normal | `201` |
| `require_approval` | Held for approval | `pending_authorization` | `202` |
| `delay` | Held for a time lock | `delayed`, with `execute_at` set | `202` |
| `block` | Rejected, `policy.violation` sent | Not signed | Error |

A held transaction is persisted and its funds are locked. It does not need to be resubmitted: it executes on its own once it is approved or its time lock expires. [Handle held and rejected transactions](https://www.bitnob.dev/docs/platform/policies/handle-held-and-rejected-transactions.md) covers the integration side.

## Categories

Every policy has a `category`: `access`, `whitelist`, `limits`, `approvals`, `velocity`, `compliance`, or `quorum`. If you leave it out, it defaults to `access`. The category describes what the policy is for and controls how it is evaluated. `quorum` is reserved for the policy that governs administrative changes, described in [Policy lifecycle and quorum](https://www.bitnob.dev/docs/platform/policies/policy-lifecycle-and-quorum.md).

[Rule reference](https://www.bitnob.dev/docs/platform/policies/rule-reference.md#policy-fields) lists the categories.

## Policies and KYT screening

Policies decide whether a transfer may be signed. [KYT risk profiles](https://www.bitnob.dev/api-reference/platform/kytrisk-profiles/list-risk-profiles.md) screen the counterparties a transfer touches, and can hold or block by risk category and exposure. They are configured separately, and a transfer must pass both.


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