# Trading through the API

> Prepare, sign or execute actions, idempotency and a complete example.

Source: https://docs.botradix.com/api-orders/

Every trading action starts with **prepare**: BotRadix validates the action against the market, your account and the contracts, simulates it, and returns exactly what must be signed or sent. Nothing is executed by `prepare`.

## The flow

1. `POST /orders/prepare` with an action, its payload and an idempotency key.
2. Check the response: `execution.ready` must be `true`.
3. Execute it, depending on `execution.status`:

| `execution.status` | What to do next |
|---|---|
| `signature_ready` | Sign `execution.typed_data` (EIP-712) with the wallet and send it to the relay. Used today to open and close positions |
| `ready` | Sign and broadcast each transaction in `execution.transactions`, in order, from the wallet |
| `preflight_failed` | Not executable now. `execution.reason` says why (for example `withdraw_margin_insufficient` or `oracle_unavailable`) |
| `contract_pending` | The market or contract is not available for this action yet |

> [!WARNING]
> `prepare` answers HTTP 200 even when the action cannot be executed. Always check `execution.ready` and `execution.status` before signing anything.

A prepared action expires: 120 seconds for trading actions, 180 seconds for collateral actions and 300 seconds for signed intents. Prepare again if it expires.

## Path 1 — Signed intent (open and close positions)

For `open_position`, `close_position_market` and `close_position_limit`, the response contains an EIP-712 message instead of transactions:

- `execution.mode` is `signed_intent` and `execution.transactions` is empty.
- The typed data uses the domain `BotradixDEX`, version `1`, chain ID `8453`, and the DEX router as verifying contract.
- The wallet signs it (a signature, not a transaction), and you send it to the relay:

```bash
curl -X POST https://app.botradix.com/api/dex/actions/relay \
  -H "Content-Type: application/json" \
  -d '{"wallet":"0xYourWallet","intent_id":"dx_…","signature":"0x…"}'
# → {"tx_hash":"0x…","state":"submitted"}
```

BotRadix's executor then broadcasts the transaction. Its network cost is charged in USDC from your DEX collateral and is shown beforehand in `execution.costs`, so the wallet does not need ETH for these actions.

> [!NOTE]
> The relay endpoint is currently part of the unversioned web API (`/api/dex/actions/relay`). It is the supported way to submit signed intents today.

## Path 2 — Wallet transactions

For the other actions (collateral, TP/SL updates, isolated-margin changes, cancel and replace), `execution.transactions` lists one or more transactions:

```json
{ "kind": "action", "from": "0xYourWallet", "to": "0x…", "data": "0x…", "value": "0x0", "deadline": 1790950518 }
```

A `kind: "approval"` entry (an exact-amount token approval) may come first for deposits. Sign and broadcast them in order from the same wallet; they need a little ETH on Base for gas.

**USDC deposit by permit (no gas).** When `GET /api/dex/config` reports `collateral_model.approval.permit: true`, add `"permit": true` next to `action` for an `add_collateral` of USDC. The response then has `execution.mode: "permit_deposit"` with EIP-2612 `typed_data`: sign it with the wallet and send `{"wallet","intent_id","signature"}` to `POST /api/dex/actions/relay`. A protocol executor submits the deposit and pays the gas. Without the flag, or for BAQ, you receive the approval and deposit transactions.

If you broadcast transactions yourself, report the hash to `POST /api/dex/actions/receipt` (`{"wallet","intent_id","tx_hash","state"}`) to receive `transaction.*` webhooks for it; otherwise BotRadix only learns about the trade when it is indexed from the chain.

## Path 3 — Delegated execution

`POST /orders/execute` submits actions through an executor bound to the API key (scope `trade:execute`; `orders:manage` for cancel, replace and TP/SL primitives). The response is `202` with `delegated.command_id`, `delegated.tx_hash` and a `status_url`; follow it with `GET /executions/{command_id}`. Send `"prepare_only": true` to validate without submitting.

Accepted actions include `open_position`, `batch` (1 to 32 actions), `open_market`, `create_limit`, `create_stop`, `increase`, `reduce`, `close`, `cancel`, `replace`, `set_tp`, `clear_tp`, `set_sl`, `clear_sl`, `invert` and `isolated_manage`. If no executor is bound to the key, the API answers `403 delegated_executor_binding_required`.

## Idempotency

`/orders/prepare` requires an idempotency key, taken from (in this order) the `Idempotency-Key` header, `payload.idempotency_key` or `payload.client_order_id`. It must match `^[A-Za-z0-9._:-]{8,128}$`.

- Keys are scoped to wallet, action and key, and are remembered for 24 hours.
- The same key with the same payload returns the original preparation (`idempotent_replay: true`).
- The same key with a different payload returns `409 IDEMPOTENCY_KEY_CONFLICT`; a reused `client_order_id` with a different payload returns `409 DUPLICATE_CLIENT_ORDER_ID`.
- The request is matched on what you sent (`payload`, plus `permit` for deposits), for every action including `open_position`. The body is validated before the key is looked up, so a retry while the market is stale or moved returns that validation error. A replayed preparation can already be expired: check `expires_at_ms`.

`/orders/execute` reads the key only from `payload.idempotency_key` or `payload.client_order_id` and follows the same rules: the same key with the same payload replays the original response, and a different payload returns `409 IDEMPOTENCY_KEY_CONFLICT`.

## `open_position` payload

| Field | Rules |
|---|---|
| `pair_id` | Market `id` from `/markets` |
| `side` | `long` or `short` |
| `order_type` | `market` (default), `limit` or `stop` |
| `margin_mode` | `cross` (default) or `isolated` |
| `leverage` | Integer from 1 up to the market maximum (see [Leverage and margin](https://docs.botradix.com/margin/)) |
| `order_value_usdc` | Position size (notional) in USDC, up to 6 decimals, within the market minimum and maximum |
| `reference_price` | Required for market orders. Rejected with `PRICE_DEVIATION_EXCEEDED` if more than 1.5% from the server price, or `MARKET_DATA_STALE` if prices are older than 8 seconds |
| `max_slippage_bps` | Required for market orders, 1 to 1000 (0.01% to 10%) |
| `limit_price` / `trigger_price` | Required for limit / stop orders |
| `time_in_force` | `gtc`, `ioc` or `fok`; market orders are always `ioc` |
| `rest_if_unfilled` | Market orders only: keep the unfilled part as a limit order |
| `post_only` | Limit orders only |
| `take_profit`, `stop_loss` | Optional; must be on the correct side of the entry price |
| `client_order_id` | Up to 96 characters; generated if omitted |

## Example

```bash
curl -X POST https://app.botradix.com/api/dex/v1/orders/prepare \
  -H "X-BotRadix-API-Key: $BOTRADIX_API_KEY" \
  -H "Idempotency-Key: mm01-2026-10-02-0001" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "open_position",
    "payload": {
      "pair_id": 1,
      "side": "long",
      "order_type": "market",
      "margin_mode": "cross",
      "order_value_usdc": "100",
      "leverage": 5,
      "reference_price": "2500.00",
      "max_slippage_bps": 50,
      "client_order_id": "mm01-2026-10-02-0001"
    }
  }'
```

The response contains `intent_id`, the normalized `payload`, `execution` (status, typed data or transactions, simulation, costs and deadline) and `expires_at_ms`.

## After execution

- `GET /transactions` lists your intents and their on-chain state.
- `GET /native/executions/{tx_hash}` reports finality: `PENDING`, `PENDING_FINALITY` (fewer than 8 confirmations), `REVERTED` or `FINAL`.
- [Webhooks](https://docs.botradix.com/api-realtime/) notify order, position, collateral and transaction changes.
