# Orders

Every order type, how the engine fills it, time in force, sizes and prices, and how to never place an order twice.

An order is one `POST` to `/v1/accounts/{account_id}/orders`. This page explains what each field does and, just as important, exactly how the engine fills what you send. A bot that knows the fill rules never gets surprised by them.

## Order types

| `type` | What it does | How it fills |
|---|---|---|
| `market` | Trades now | The whole size at the ask (buy) or the bid (sell) |
| `limit` | Waits for a better price | At your limit, once the last price trades strictly through it |
| `stop_market` | Waits for the price to break a level, then trades | At the ask plus one tick (buy) or the bid minus one tick (sell) once the last price reaches the stop |
| `stop_limit` | Like a stop, but never worse than your limit | At the last price, once it is at or through the stop and strictly better than the limit |
| `take_market` | Takes profit at a level, then trades | Like `stop_market`, on the profit side |
| `take_limit` | Takes profit at a level, never worse than your limit | Like `stop_limit`, on the profit side |

Two facts shape everything else:

- **Fills are whole.** An order fills completely or not at all. There are no partial fills, so `filled_size` is `0` or the full size.
- **A limit already through the market fills at once,** at the ask or bid, which can be worse than your limit if the limit sits inside the spread. The API refuses that case with `limit_inside_spread` instead, so you never pay more than you meant to. Send `post_only: true` to be refused whenever an order would fill at once.

## Sizes and prices

Sizes are in the base asset (BTC for `BTC-USD`) and must be a multiple of the market's `size_step`. Prices must be a multiple of its `tick_size`. Both are in `GET /v1/markets`. A size off the step is refused with `invalid_size_step`, and the error's `details.nearest` gives the two valid sizes around yours.

Send `notional_usd` instead of `size` to size in dollars: the API rounds down to the step.

Prices, sizes and money are decimal strings (`"62500.00"`), never floats, so nothing is lost to rounding.

## Time in force

| Type | Choices | Default |
|---|---|---|
| `market` | `ioc`, `fok` | `ioc` |
| `limit` | `gtc`, `gtd`, `ioc`, `fok` | `gtc` |
| stops and takes | `gtc`, `gtd` | `gtc` |

`gtd` needs `expires_at`, in Unix milliseconds. Because fills are whole, `fok` and `ioc` behave the same.

## Slippage

A market order never fills worse than its worst price. Set it in percent with `max_slippage_pct`, or as a price with `worst_price`. Without either, the market's default applies; `GET /v1/markets` shows it with its bounds. When the market moved past your worst price, the order is refused with `slippage_exceeded` and nothing fills.

## Protecting a position

Attach exits to the order itself, and they are placed the moment it fills:

```json
{
  "market": "BTC-USD",
  "side": "buy",
  "type": "limit",
  "size": "0.010",
  "limit_price": "62500.00",
  "take_profit": { "price": "64000.00" },
  "stop_loss": { "price": "61800.00", "trailing": { "distance": "350.00" } },
  "break_even": { "trigger_price": "63100.00", "offset_ticks": 2 }
}
```

The exits live on our servers as reduce-only orders: when one fills, the other is canceled, and they shrink if you reduce the position. They keep protecting you if your bot goes offline. Change them later with `PUT .../positions/{market}/exits`.

## Never place an order twice

Send an `Idempotency-Key` header with every order: any unique string, like a UUID. If the connection drops and you retry with the same key and the same body, you get the first order back instead of a second one. The key is remembered for 24 hours. The SDKs do this for you.

You can also give the order your own `client_order_id` and find it later with `GET .../orders?client_order_id=`, even from another process.

## Statuses

| `status` | Meaning |
|---|---|
| `pending` | Accepted by the API, on its way to the engine |
| `working` | Resting on the engine, waiting for its price |
| `filled` | Done. `average_fill_price` and `fee` are set |
| `canceled` | Canceled; `cancel_reason` says why |
| `rejected` | Refused; `reject.code` says why |
| `expired` | An `ioc`, `fok` or `gtd` order that ended unfilled |
| `unconfirmed` | The engine's answer was lost on the way. Read the order again in a second: it settles to one of the above |

A market order's answer waits up to two seconds for the fill, so you usually get `filled` straight away.

## When the engine restarts

If the trading engine restarts, it cancels every working order with `cancel_reason: system_restart`, and positions it held are closed. The API tells you on the account stream and your webhooks, and places nothing back by itself: your bot decides what to re-place.
