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

# Orders

> Place an order on a quote you hold: TetraFi executes it through your venue account or an organisation wallet granted to your key.

You place an order by naming a quote you hold. TetraFi kept the quote's terms when it returned them, so the order body is the quote id plus anything only you can supply, such as the least you accept to receive.

> [!NOTE]
>
> The API may change until launch. Every change is listed in the [Changelog](https://tetrafi.mintlify.site/changelog.md).

## Basic and Advanced orders

Every quote names how it executes in `legs[].venue.integrationMode`:

| Mode | Where it executes | What you do before ordering |
| - | - | - |
| `basic` | TetraFi executes it for you, from one of your workspace's exchange or OTC venue accounts or from an organisation wallet granted to your API key, only inside the limited grant that wallet's owners approved. | Nothing. No wallet signature. |
| `advanced` | Your own wallet signs and settles on-chain, through escrow. | Not open yet. |

> [!NOTE]
>
> Advanced execution is not open yet: an order on an Advanced quote answers `403 ADVANCED_MODE_DISABLED`, and Advanced venues are listed in `sources` as `unavailable` with the reason `advancedModeDisabled`. This page describes Basic orders, the ones you can place today.

The quote's `next` tells you the next call: `order` means place the order now; `preflight`, `sign` and `transact` appear only on Advanced quotes; `fund` means the paying account is short; `requote` means ask the venue again ([Requotes and expiry](https://tetrafi.mintlify.site/trade/requotes-and-expiry.md)).

## Place an order

`POST /api/v1/orders`

| Field | Required | Value |
| - | - | - |
| `quoteId` | Yes | The quote id (`q_…`) from `POST /api/v1/quotes`. |
| `minReceive` | When the quote's `minReceiveRequired` is `true`. Optional otherwise. | The least you accept to receive, in units of the Asset you buy, such as `"4.99"`. |

Leave out `signature` and `priceProtectionBps`: they are for Advanced quotes, and a Basic quote refuses them.

An API key must send an `Idempotency-Key` header with every order. A retry with the same key and body returns the first answer instead of placing a second order ([Idempotency](https://tetrafi.mintlify.site/trade/idempotency.md)).

```bash cURL theme={null}
# Generate one Idempotency-Key per order and store it with the order before you send it.
idempotency_key=$(openssl rand -hex 16)
order_body="{\"quoteId\":\"$order_quote\"}"
placed=$(tetrafi POST /api/v1/orders "$order_body" "Idempotency-Key: $idempotency_key")
# A basicTrade names its venue trade in operation; an advancedTrade names its order in order.
jq -c '{kind, quoteId} + if .kind == "basicTrade" then {operation: (.operation | {connectionId, operationId})}
  else {order: (.order | {orderId})} end' <<<"$placed" >&2
```

A Basic quote answers `"kind": "basicTrade"` with the venue trade it created:

```json Response (shortened) theme={null}
{
  "kind": "basicTrade",
  "orderId": "acb84378-…",
  "quoteId": "q_01M5XJXR…",
  "status": "working",
  "terminal": false,
  "statusUrl": "/api/v1/orders/acb84378-…",
  "operation": {
    "connectionId": "15862432-…",
    "operationId": "acb84378-…"
  }
}
```

Every order answer carries `status`, `terminal` and `statusUrl`, and [`GET /api/v1/orders/{id}`](https://tetrafi.mintlify.site/trade/order-status) at `statusUrl` answers the same envelope.

Each quote can be ordered once. Follow the order with [Order status](https://tetrafi.mintlify.site/trade/order-status.md).

The answer comes as soon as the order is recorded, not when it completes. A Basic order on an on-chain venue answers `working` before its wallet signs. If the wallet then refuses to sign, nothing is sent and the order ends `unfilled`, with the cause in `operation.dispatch.notSentCause`, rather than as a refusal of the request. Follow every order to `terminal`, by polling `statusUrl` or with [webhooks](https://tetrafi.mintlify.site/trade/webhooks.md). A `2xx` answer only means the order exists.

## Basic orders

A Basic order is executed for you, on your venue account with that account's balance, or from an organisation wallet granted to your key, only inside the limited grant that wallet's owners approved. You need an API key with venue trading and request signing ([Trading on your venue accounts](https://tetrafi.mintlify.site/authentication.md#trading-on-your-venue-accounts)), and the account or wallet must hold what you sell. If a venue account holds less, the order is refused with `409 FUNDING_SHORTFALL` and `next.kind: "fund"`: fund the account, then get a new price.

A `signature` is refused with `400 SIGNATURE_NOT_ACCEPTED`.

### Venue quotes that execute within your limit

Some venues execute within your limit: you name the least you accept, and the venue is asked for its price when you order. They are:

* desks whose firm prices last only a few seconds, too short to confirm;
* on-chain venues, such as bridges, that TetraFi trades for you from an organisation wallet, inside the limited grant its owners approved.

Their quotes are often indicative with `next.kind: "order"`. On such a venue, a quote that is not firm reads `minReceiveRequired: true` and must be ordered with `minReceive`: the order trades only if the venue delivers at least that. Without `minReceive`, it is refused with `400 MIN_RECEIVE_REQUIRED`, with `details.field` `minReceive`, and nothing is sent. Read the flag rather than working it out from `firm` and `expiresAt`.

You can also send `minReceive` on any other Basic quote to set your own floor, and the order then trades only at or above it. If the venue's held firm price already delivers less, the order is refused with `409 VENUE_QUOTE_OUTSIDE_BOUNDS` and nothing is sent.

## Price protection and minimum receive

Which field applies where:

| Where | Field | Range | Applies to |
| - | - | - | - |
| Quote request or requote | `priceProtectionBps` | 0 to 1000, 30 when omitted | Every non-firm executable quote. It sets the quote's `minOut`, and `maxPay` on a buy-exact quote (`buyAmount`) from a venue account ([Sort and price protection](https://tetrafi.mintlify.site/search/sort-and-protection.md#price-protection)). |
| Order | `minReceive` | A positive amount | Required when the quote's `minReceiveRequired` is `true` ([above](#venue-quotes-that-execute-within-your-limit)), optional on any other quote. |

## Cancel an order

`POST /api/v1/orders/{id}/cancel` cancels an order that has not been sent to a venue yet, or a pay-in still waiting for your funds. It answers the order envelope with `status` `cancelled` and `terminal` `true`, and cancelling it again changes nothing. Send an `Idempotency-Key`, as when you place it; the key needs **Submit orders**.

Once the order has been sent to a venue, its funding or delivery has moved, or it has ended, the cancel is refused with `409 ORDER_NOT_CANCELLABLE` and the order's status in `details.status`. TetraFi never cancels at the venue and never refunds: a Basic venue trade is sent as soon as it is placed, so in practice you can cancel one only while it waits for a pay-in.

```bash cURL theme={null}
# POST /api/v1/orders/{id}/cancel cancels an order that has not been sent to a venue yet, or a
# pay-in still waiting for funds. Once it is sent, or final, it answers 409 ORDER_NOT_CANCELLABLE
# with the order's status in details.status, and nothing changes. A cancel needs an
# Idempotency-Key, like placing an order.
order_id=$(jq -r '.orderId' <<<"$final")
if refused=$(tetrafi POST "/api/v1/orders/$order_id/cancel" '' "Idempotency-Key: $(openssl rand -hex 16)" 2>/dev/null); then
  fail EXPECTED_ORDER_NOT_CANCELLABLE
fi
jq -c '{error, status: .details.status}' <<<"$refused" >&2
```

```json Output on test networks theme={null}
{"error":"ORDER_NOT_CANCELLABLE","status":"unfilled"}
```

## Refusals

A refused order answers the standard error body: `error`, `message`, and `next` or `details` where present. Unless the table says otherwise, nothing was sent.

| Code | Status | Why | What to do |
| - | - | - | - |
| `INVALID_REQUEST` | 400 | A field is unknown or malformed, or `priceProtectionBps` was sent on a Basic quote. `message` names it. | Fix that field. |
| `SIGNATURE_NOT_ACCEPTED` | 400 | A signature was sent on a quote that takes none. | Leave out `signature`. |
| `MIN_RECEIVE_REQUIRED` | 400 | This venue executes within your limit and the quote is not firm. | Send `minReceive`. |
| `MIN_RECEIVE_INVALID` | 400 | `minReceive` is not a positive amount. | Send it as a positive decimal string. |
| `ADVANCED_MODE_DISABLED` | 403 | The quote is Advanced, which is not open yet. | Order a Basic quote instead. |
| `IDEMPOTENCY_KEY_REQUIRED`, `IDEMPOTENCY_KEY_INVALID` | 400 | The `Idempotency-Key` header is missing or malformed. | See [Idempotency](https://tetrafi.mintlify.site/trade/idempotency.md). |
| `QUOTE_NOT_FOUND` | 404 | No quote with this id is available to you. | Check the id. |
| `QUOTE_NOT_EXECUTABLE` | 409 | The quote is a price for comparison, not executable. `next` is `requote`. | Requote it to get an executable price. |
| `QUOTE_NOT_HELD` | 409 | The quote is no longer held. `next` is `requote`. | Search again. |
| `QUOTE_ALREADY_ORDERED` | 409 | Another order already used this quote. | Read it with [`GET /api/v1/orders/by-quote/{quoteId}`](https://tetrafi.mintlify.site/trade/idempotency#recover-an-order-by-its-quote). |
| `VENUE_QUOTE_OUTSIDE_BOUNDS` | 409 | The venue's held firm price is below your `minReceive`. | Lower `minReceive` or get a new price. |
| `FUNDING_SHORTFALL` | 409 | The venue account holds less than the trade needs. `next` is `fund`. | Fund the account, then get a new price. |
| `IDEMPOTENCY_CONFLICT`, `ORDER_IN_PROGRESS` | 409 | The key was used for a different body, or an order with it is still being placed. | See [Idempotency](https://tetrafi.mintlify.site/trade/idempotency.md). |
| `QUOTE_EXPIRED` | 410 | The quote's `expiresAt` has passed. `next` is `requote`. | [Requote it](https://tetrafi.mintlify.site/trade/requotes-and-expiry.md#refresh-an-expired-quote). |
| `REQUOTE_WINDOW_CLOSED` | 410 | The quote is past its `retainUntil`, so it can no longer be requoted or ordered. `next` is `search`. | Search again. |
| `QUOTE_NOT_YET_DURABLE`, `QUOTE_STORE_UNAVAILABLE` | 503 | The quote is still being recorded, or held quotes cannot be read right now. | Send the same request with the same key after a second. |

## In the app

On the Trade page, choosing a quote and confirming it in Review places this same order.


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