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

# Trade Desk Orders

> End-to-end guide to the orders flow: quote, order creation, funding, manual desk execution, completion, cancellation, refunds, and sandbox testing

Orders are Zuba's channel for large currency transfers that are executed manually by our trade desk rather than dispatched straight through to a payment rail. You request a firm quote, create the order against it, fund it, and Zuba's desk sources liquidity and delivers to your beneficiary within an execution window stated on the order.

The flow is deliberately different from payouts in three ways:

* **Quote-first.** Every order starts from a firm quote (`POST /v1/quotes` with `intent: "order"`). There is no auto-priced path.
* **Fund-first.** The desk executes only against funds that are already secured. If your Zuba balance covers the order it is funded at creation; otherwise the order returns funding instructions and waits for your transfer. Zuba never extends credit, and nothing moves outbound until the order is funded.
* **Manually executed.** A human trading team fills the order and a second operator approves the execution before any funds move. Orders complete within the execution window returned on the order, not in seconds.

If you need automated, straight-through delivery for everyday amounts, use [payouts](/concepts/payouts) instead. Amounts above a payout corridor's cap are served by this channel.

## Orders vs payouts

|              | Payouts                                     | Orders                                                             |
| ------------ | ------------------------------------------- | ------------------------------------------------------------------ |
| Execution    | Automated, straight-through                 | Manual, by Zuba's trade desk                                       |
| Pricing      | Quote or auto-priced; rate plus a fixed fee | Always quote-first; a single all-in rate, no separate fee          |
| Funding      | Your balance, at creation                   | Your balance, or a per-order transfer against funding instructions |
| Typical size | Everyday amounts, subject to corridor caps  | Large transfers, including amounts above payout corridor caps      |
| Speed        | Seconds to minutes                          | Within the execution window on the order                           |
| Cancellation | While still pending                         | Any time before the desk approves execution                        |

## Prerequisites

* **Orders enabled on your account.** Orders are enabled per account, configured with the currency pairs (corridors) and transaction limits agreed during onboarding. Contact your account manager to get set up.
* **A beneficiary** holding an **active account in the buy currency** (see [Beneficiaries](/api-reference)).
* Valid API credentials (see [Authentication](/authentication)).

The examples below use the sandbox host `https://api.sandbox.zuba.com`. In production, swap it for `https://api.zuba.com`; everything else is identical.

## Step 1: Get a firm quote

Request a quote with `intent: "order"`. Fix exactly one side of the trade:

* `fromAmount` fixes what you sell (the total debit), or
* `toAmount` fixes what your beneficiary receives.

```bash theme={"dark"}
curl -X POST "https://api.sandbox.zuba.com/v1/quotes" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "intent": "order",
    "fromCurrency": "USDT",
    "toCurrency": "NGN",
    "toAmount": "1500000.00"
  }'
```

```json theme={"dark"}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "fromCurrency": "USDT",
  "toCurrency": "NGN",
  "targetAmount": "1500000.0000",
  "totalDebitAmount": "1006.5000",
  "rate": "0.00067100",
  "expiresAt": "2026-07-08T12:00:30Z"
}
```

Three things to know about order quotes:

* **The rate is all-in.** `totalDebitAmount` is exactly what the order costs you; there is no separate fee added at order creation. `rate` is expressed as `fromCurrency` per 1 `toCurrency`.
* **The quote is firm.** The rate you see is the rate your order executes at, regardless of market movement while the desk works the order.
* **The quote is single-use and short-lived.** It typically expires within about 30 seconds; always read `expiresAt` rather than assuming a TTL. Create the order before it expires, or request a fresh quote.

A quote request is rejected with `UNSUPPORTED_CORRIDOR` if the pair is not enabled for orders on your account, or with an amount error if it is below the corridor minimum.

## Step 2: Create the order

Create the order with the quote id and a beneficiary. The `Idempotency-Key` header is required.

```bash theme={"dark"}
curl -X POST "https://api.sandbox.zuba.com/v1/orders" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4d9f6c2a-8f6e-4b1a-9d3e-2c7b5a1e0f48" \
  -d '{
    "quotationId": "550e8400-e29b-41d4-a716-446655440000",
    "beneficiaryId": "cc82fa1d-fc7a-478c-a734-d3bce40464e7",
    "clientRef": "ORD-2026-0001",
    "purpose": "Supplier settlement"
  }'
```

Creation consumes the quote (it cannot be used again) and picks a funding path based on your available balance in the sell currency:

**If your balance covers `fromAmount`**, it is debited into a hold reserved for this order in the same step, and the order is created directly in `funds_received`, queued for the desk:

```json theme={"dark"}
{
  "id": "9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d",
  "quoteId": "550e8400-e29b-41d4-a716-446655440000",
  "beneficiaryId": "cc82fa1d-fc7a-478c-a734-d3bce40464e7",
  "status": "funds_received",
  "fromCurrency": "USDT",
  "toCurrency": "NGN",
  "fromAmount": "1006.5000",
  "toAmount": "1500000.0000",
  "rate": "0.00067100",
  "clientRef": "ORD-2026-0001",
  "purpose": "Supplier settlement",
  "fundingDeadlineAt": "2026-07-08T12:30:00Z",
  "executionDeadlineAt": "2026-07-08T18:00:00Z",
  "fundedAt": "2026-07-08T12:00:05Z",
  "createdAt": "2026-07-08T12:00:05Z",
  "updatedAt": "2026-07-08T12:00:05Z"
}
```

**Otherwise**, no money moves: the order is created in `awaiting_funds` and the response carries a `settlement` block with the funding instructions, the exact amount to transfer, and a unique reference:

```json theme={"dark"}
{
  "id": "1b7e4a90-2c5d-4e8f-9a0b-3c6d7e8f9a0b",
  "quoteId": "6a1f9511-f3ac-52e5-b827-557766551111",
  "beneficiaryId": "cc82fa1d-fc7a-478c-a734-d3bce40464e7",
  "status": "awaiting_funds",
  "fromCurrency": "XOF",
  "toCurrency": "USD",
  "fromAmount": "6500000.0000",
  "toAmount": "10000.0000",
  "rate": "650.00000000",
  "clientRef": "ORD-2026-0002",
  "settlement": {
    "settlementId": "8c2d5e7f-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
    "method": "virtual_account",
    "requiredAmount": "6500000.0000",
    "currency": "XOF",
    "reference": "SETTLE-4F7A2C91B0",
    "status": "open",
    "instructions": {
      "bankName": "Example Bank",
      "accountNumber": "00012345678",
      "accountName": "Zuba Collections",
      "note": "Include the settlement reference in the transfer narration."
    },
    "expiresAt": "2026-07-08T12:30:00Z"
  },
  "fundingDeadlineAt": "2026-07-08T12:30:00Z",
  "createdAt": "2026-07-08T12:00:05Z",
  "updatedAt": "2026-07-08T12:00:05Z"
}
```

Either way the quoted rate is locked: funding the order within the window executes at the rate on the order, regardless of market movement in between.

**Idempotency.** Retrying the same request returns the original order rather than creating a duplicate. Two independent guards apply: the quote is single-use (a retry carrying the same `quotationId` returns the order it already created), and `clientRef` is unique per account (reusing one on a different order returns `409 CLIENT_REF_ALREADY_USED`).

## Step 3: Fund the order

Balance-funded orders skip this step; they are already `funds_received`.

For a settlement-funded order, transfer `requiredAmount` to the `instructions` before `fundingDeadlineAt` (mirrored on `settlement.expiresAt`):

* **Bank transfer** (`method: "virtual_account"`): send to the account in `instructions` and **include the `reference` in the transfer narration** wherever your bank supports one. A referenced transfer attributes to the order at any amount: a short payment sits on your balance until you top it up under the same reference, and an overpayment attributes with the excess left on your balance. Without the reference, only a transfer of exactly `requiredAmount` auto-matches; anything else is credited to your balance and attributed manually.
* **Crypto transfer** (`method: "wallet_address"`): send **exactly** `requiredAmount` to your own deposit address from `instructions.addresses` (any listed network). The receiving address identifies you, so no reference is needed.

When the transfer is matched, the funds are held into the order, the status moves to `funds_received`, `executionDeadlineAt` is set, and the `order.funds_received` webhook fires. Matching is automatic on referenced bank transfers and crypto deposits; unreferenced bank transfers can take longer when manual attribution is needed.

If the funding window closes first, the order lands `expired` (webhook `order.expired`) and the settlement is closed. Nothing was held, so there is nothing to refund; a transfer that arrives after expiry is simply credited to your balance. Re-quote and create a fresh order, which your now-funded balance can fund directly.

## Step 4: Wait for desk execution

This is the part that makes orders different: **there is nothing to call.** Your order is now in a queue worked by Zuba's trading team. An operator fills the order at or better than your quoted rate, and a second operator independently reviews and approves the execution before any funds move. This dual-control step is why orders are not instant.

What you can rely on:

* The order completes or fails by `executionDeadlineAt`. If the desk has not executed by then, the order automatically fails with reason `review_expired` and your held funds are returned. You never need to chase a stuck order.
* While the desk works, the order reports `funds_received`. Once execution is approved it briefly reports `executing`; at that point it can no longer be cancelled.
* Desk internals (venues, fills, operators) are never exposed on the API. Your contract is the quoted rate and the execution window.

Track progress by webhook (recommended) or by polling:

```bash theme={"dark"}
curl "https://api.sandbox.zuba.com/v1/orders/9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

## Step 5: Completion

When the desk delivers to your beneficiary, the order lands `completed` and the `order.completed` webhook fires. For orders delivered over a wire rail, the response and webhook carry the outbound wire reference (`uetr`) for your beneficiary's bank to trace the payment.

```json theme={"dark"}
{
  "id": "9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d",
  "status": "completed",
  "uetr": "e8b1a9d2-7c4f-4a2b-8e6d-1f3a5b7c9d0e",
  "completedAt": "2026-07-08T14:21:07Z",
  "...": "..."
}
```

The hold is settled: you paid exactly `fromAmount` at the quoted rate, and the ledger shows the order's legs against the order id. See [Ledger](/concepts/ledger) for how to reconcile.

## Order lifecycle

```mermaid theme={"dark"}
%%{init: {
  "theme": "base",
  "themeVariables": {
    "fontFamily": "'Hanken Grotesk', system-ui, sans-serif",
    "fontSize": "13px",
    "primaryColor": "#fbf4ea",
    "primaryBorderColor": "#d8c8ad",
    "primaryTextColor": "#323236",
    "lineColor": "#a99e8c",
    "tertiaryColor": "#efe3d2",
    "tertiaryBorderColor": "#d8c8ad",
    "tertiaryTextColor": "#5b5347"
  }
}}%%
stateDiagram-v2
    direction LR

    [*] --> funds_received : create, balance covers (funds held)
    [*] --> awaiting_funds : create, funding instructions issued
    awaiting_funds --> funds_received : transfer matched (funds held)
    awaiting_funds --> expired : funding window lapses
    awaiting_funds --> cancelled : you cancel (nothing held)
    funds_received --> executing : desk approves execution
    funds_received --> cancelled : you cancel (funds returned)
    funds_received --> failed : desk rejects or window lapses (funds returned)
    executing --> completed : delivered (uetr recorded)
    executing --> failed : execution error (funds returned)

    completed --> [*]
    failed --> [*]
    expired --> [*]
    cancelled --> [*]
```

| Status           | Meaning                                                                                                                                                                                                       |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `created`        | The order exists but no funding path has resolved yet. Transient: creation moves straight on to `funds_received` or `awaiting_funds`, so polling never observes it (the `order.created` webhook still fires). |
| `awaiting_funds` | Funding instructions are issued and the order is waiting for your transfer. No funds are held.                                                                                                                |
| `funds_received` | The source amount is held and the order is queued for the desk.                                                                                                                                               |
| `executing`      | The desk has approved execution. The order can no longer be cancelled.                                                                                                                                        |
| `completed`      | Delivered to your beneficiary. `uetr` is set for wire deliveries. Terminal.                                                                                                                                   |
| `failed`         | The desk could not execute, or the execution window lapsed. `failureReason` is set and the held funds are returned to your balance in full. Terminal.                                                         |
| `expired`        | The funding window closed before the order was funded. Nothing was held; late-arriving funds credit your balance. Terminal.                                                                                   |
| `cancelled`      | You cancelled before execution was approved. Any held funds are returned in full. Terminal.                                                                                                                   |

## Webhooks

Order webhooks share the envelope, signing, and retry behaviour described in [Webhook Notifications](/guides/webhooks). Subscribe your endpoint to the `order.*` events:

| Event                  | Fires when                                                                                                               |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `order.created`        | The order is created. Arrives together with `order.funds_received` or `order.awaiting_funds`.                            |
| `order.awaiting_funds` | Funding instructions are issued; the payload's `settlement` block carries the account or address, amount, and reference. |
| `order.funds_received` | Funds are held and the order is queued for the desk.                                                                     |
| `order.completed`      | The desk delivered; `uetr` present for wire deliveries.                                                                  |
| `order.failed`         | The order failed; `failureReason` set, held funds returned.                                                              |
| `order.expired`        | The funding window closed before the order was funded.                                                                   |

There is no webhook for `executing` (desk review is internal) and none for `cancelled` (cancellation is your own synchronous API call).

Every event for a settlement-funded order carries the `settlement` block, so you can correlate by reference at any point in the lifecycle; on balance-funded orders `settlement` is `null`. Example delivery:

```json theme={"dark"}
{
  "id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "order.completed",
  "createdAt": "2026-07-08T14:21:08.000Z",
  "test": false,
  "data": {
    "id": "9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d",
    "clientRef": "ORD-2026-0001",
    "status": "completed",
    "fromCurrency": "USDT",
    "toCurrency": "NGN",
    "fromAmount": "1006.5000",
    "toAmount": "1500000.0000",
    "rate": "0.00067100",
    "failureReason": null,
    "uetr": "e8b1a9d2-7c4f-4a2b-8e6d-1f3a5b7c9d0e",
    "settlement": null,
    "fundingDeadlineAt": "2026-07-08T12:30:00.000Z",
    "executionDeadlineAt": "2026-07-08T18:00:00.000Z",
    "fundedAt": "2026-07-08T12:00:05.000Z",
    "completedAt": "2026-07-08T14:21:07.000Z",
    "createdAt": "2026-07-08T12:00:05.000Z"
  }
}
```

## Cancelling an order

You can cancel an order at any point before the desk approves execution:

```bash theme={"dark"}
curl -X POST "https://api.sandbox.zuba.com/v1/orders/9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d/cancel" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

A successful cancel returns the order with `status: "cancelled"`. While the order is `awaiting_funds` nothing is held, so cancellation simply closes it; after `funds_received` the held funds are returned to your balance in full. Retrying a successful cancel returns the already-cancelled order.

Once execution has been approved (`executing` or any terminal status), cancel returns `409 ORDER_NOT_CANCELLABLE`. Because approval can happen at any moment while the order is `funds_received`, treat cancellation as a race you can lose: a `409` means the trade is executing and will complete at the quoted rate.

## Failures and refunds

A failed order is terminal and always refunds the full held amount to your balance automatically; there is no partial execution and nothing to claim. `failureReason` carries a fixed taxonomy:

| `failureReason`    | Meaning                                                          | What to do                                                                                                     |
| ------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `execution_failed` | The desk could not execute the order.                            | Funds are back on your balance. Request a fresh quote and create a new order, or contact support if it recurs. |
| `review_expired`   | The execution window lapsed before the desk completed the order. | Same: funds are returned; re-quote and retry.                                                                  |

<Note>
  A `failed` order is a normal, fully-resolved outcome, not a stuck state:
  the terminal status and the refund arrive together. Refunds always land on
  your Zuba balance, never as an outbound transfer back to you; withdraw them
  with a payout if you need them off-platform. Reconcile by matching the hold
  and its reversal against the order id in your
  [ledger history](/concepts/ledger).
</Note>

An `expired` order held nothing, so there is no refund; any transfer that arrives after expiry is credited to your balance.

## Listing orders

`GET /v1/orders` returns your orders newest-first with cursor pagination, filterable by `status` and a `from`/`to` creation-time window:

```bash theme={"dark"}
curl "https://api.sandbox.zuba.com/v1/orders?status=completed&limit=20" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

Use distinct `clientRef` values per order; they surface on the order, the webhooks, and your ledger history, and make reconciliation trivial.

## Testing in the sandbox

The sandbox exercises the real order machinery end to end. Two mechanisms cover the parts that involve other actors in production:

**Deterministic desk outcomes.** Orders are executed manually in production, so two magic **sell amounts** drive a funded order through the real desk workflow to a terminal state without a human operator. Any other amount stays in `funds_received` awaiting manual execution, exactly as in production.

| Sell amount | Outcome                                                                            |
| ----------- | ---------------------------------------------------------------------------------- |
| `1111.11`   | Lands `completed`, with a `sandbox-` prefixed `uetr`.                              |
| `2222.22`   | Lands `failed` (`execution_failed`), with the held funds returned to your balance. |

The match is on the order's source (sell) amount, is numeric (`1111.11` matches `1111.1100`), and works in any sell currency.

**Simulated settlement funding.** The [sandbox deposit endpoint](/guides/sandbox-testing#step-1-fund-your-workspace) accepts a `narration` field; include an order's settlement `reference` in it to exercise the real automatic matcher.

A complete settlement-funded run, using a fiat sell currency (settlements in crypto sell currencies fund through your own deposit addresses by exact amount, so the narration mechanism below does not apply to them):

1. Quote with the sell side pinned to a magic amount: `{"intent": "order", "fromCurrency": "USD", "toCurrency": "NGN", "fromAmount": "1111.11"}`.
2. Create the order with the returned quote id and a beneficiary holding an active account in the buy currency. With no balance in the sell currency, the order lands `awaiting_funds` and returns the `settlement` block (`order.created` + `order.awaiting_funds` fire).
3. Simulate your transfer: `POST /v1/sandbox/deposits` with `amount` set to `requiredAmount`, `currency` set to the sell currency, and `narration` containing the settlement `reference`. The matcher attributes it, holds the funds, and fires `order.funds_received`.
4. The magic sell amount then drives the desk flow: `order.completed` (or `order.failed` for `2222.22`) arrives shortly after. Do not assume a fixed delay; wait on the webhook or poll.

To test the balance-funded path instead, fund your sandbox balance first and create the order; it goes straight to `funds_received`. To test cancellation, use a non-magic amount (the order will sit in `awaiting_funds` or `funds_received`) and call the cancel endpoint. To test expiry, create a settlement-funded order and let the funding window lapse. See [Sandbox Testing](/guides/sandbox-testing#deterministic-orders) for the full sandbox reference.

## Error reference

| Status | Code                                                                                 | Cause                                                                                                                                 |
| ------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `Idempotency-Key header is required`                                                 | Missing the required header on create.                                                                                                |
| 400    | `QUOTE_NOT_FOUND` / `QUOTE_EXPIRED` / `QUOTE_ALREADY_USED` / `QUOTE_INTENT_MISMATCH` | The quote is unknown, past `expiresAt`, already consumed, or was not minted with `intent: "order"`. Request a fresh order quote.      |
| 400    | `UNSUPPORTED_CORRIDOR`                                                               | The currency pair is not enabled for orders on your account.                                                                          |
| 400    | `BENEFICIARY_NOT_FOUND`                                                              | The beneficiary id does not exist on your account.                                                                                    |
| 400    | `NO_SUITABLE_ACCOUNT_FOR_ORDER_CURRENCY`                                             | The beneficiary has no active account in the buy currency.                                                                            |
| 403    | `insufficient_scope`                                                                 | Orders are not enabled on your account.                                                                                               |
| 409    | `CLIENT_REF_ALREADY_USED`                                                            | The `clientRef` was already used on a different order.                                                                                |
| 409    | `ORDER_NOT_CANCELLABLE`                                                              | Cancel arrived after execution was approved.                                                                                          |
| 422    | `INSUFFICIENT_BALANCE`                                                               | Your balance does not cover `fromAmount` and settlement funding is not available for the sell currency on your account.               |
| 422    | limit exceeded                                                                       | The order exceeds your per-transaction or monthly limit. Limits are agreed at onboarding; contact your account manager to raise them. |

## Next steps

* [Webhook Notifications](/guides/webhooks) for endpoint setup, signature verification, and retries
* [Sandbox Testing](/guides/sandbox-testing) for the full deterministic test-value reference
* [Ledger](/concepts/ledger) for reconciling order holds, settlements, and refunds
* [Orders API reference](/api-reference) for full request and response schemas
