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

# Order lifecycle

> What happens between POST /orders and a settled position

## Matching has already happened when the call returns

`POST /v1/orders` is not an acknowledgement. By the time you have a response, your
order has been matched against the book and the result is final. The `status` on that
response is what actually happened:

| Response `status` | Meaning |
| - | - |
| `filled` | Fully traded. Nothing rests. |
| `partially_filled` | Some traded; the remainder rests on the book. |
| `open` | Nothing traded; the whole order rests. |
| `canceled` | Nothing traded and nothing rests. |

<Warning>
  An `ioc` order that finds no liquidity comes back **`canceled`**, not `accepted`.
  This is the most common surprise for a taker strategy, and the response is telling
  you the truth: nothing traded and nothing is resting, so there is nothing to poll
  for.
</Warning>

## The six statuses

| Status | Meaning |
| - | - |
| `accepted` | Received, durable state still catching up. **Reads only.** |
| `open` | Resting on the book, nothing traded yet. |
| `partially_filled` | Resting, with some quantity already traded. |
| `filled` | Fully traded. Terminal. |
| `canceled` | Canceled by you, or by `ioc`/`fok` semantics. Terminal. |
| `expired` | Ended without your asking. Terminal. |

`accepted` needs the caveat: it is a **transient state you will only ever see on a
read**, while the durable record catches up. A create or amend response never returns
it. Without knowing that, a client that sees `filled` from a create and `accepted`
from a read a moment later concludes the two endpoints disagree — they do not; one is
ahead of the other.

Treat `accepted` and `open` identically in your own bookkeeping.

## Filtering by status

`GET /v1/orders?status=` accepts group names as well as concrete statuses, and
combines them comma-separated (`open,filled`):

| Value | Matches |
| - | - |
| `resting` | `accepted`, `open`, `partially_filled` |
| `filled` | `filled` |
| `canceled` | `canceled`, `expired` |
| `all` (default) | everything |

`resting` is the one a quoter wants each cycle: it is exactly the set of orders that
still have working quantity, and it includes `accepted` so an order does not vanish
from your view while the read side catches up.

Group names and status values deliberately share no ambiguous token. `filled` means
the same thing in both. `resting` is a group name only — no order's status is
`resting`.

## Budget orders: spend cash instead of counting shares

A create sized in **cash** instead of shares: send `budget_cents` *instead of*
`qty` — "spend \$50.00 on yes" — with `side: buy` and `time_in_force` `ioc` or
`fok`. The engine sizes the order against the live book inside the match step,
fees included, so the budget is consumed exactly; there is no client-side
dollars-to-shares conversion to get wrong.

```bash theme={null}
curl -s -X POST https://api.trysoar.com/v1/orders \
  -H "X-API-KEY: $SOAR_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "ticker": "ANTHROPIC-VALUATION-26Q3-1.05T",
    "outcome": "yes", "side": "buy",
    "price": 45,
    "budget_cents": 5000,
    "time_in_force": "fok",
    "client_order_id": "sizer-0001"
  }'
```

Three things change relative to a qty order:

* **`price` is a slippage bound, not a target.** Fills execute at the resting
  prices, which may be better; nothing executes above your bound.
* **The response's `qty` and `filled_qty` are the engine-derived executed
  size**, and `spent_cents` is the exact realized cost — fill flow plus taker
  fees, cent for cent what left your balance. `spent_cents / qty` is your
  realized average cost per share.
* **A budget order never rests.** `fok` fills the full affordable size or
  rejects `422 budget_not_fillable` when the book cannot absorb the budget
  within your bound; `ioc` executes whatever is eligible and stops. The budget
  is also capped at your available cash — spending is bounded by
  `min(budget_cents, available_cents)`.

## Idempotency, and why it matters more here

`POST /v1/orders` takes a `client_order_id`: 1–64 printable ASCII characters, yours
to choose, **unique per user**. Not globally unique — you never need to coordinate
with anyone else's ids.

It does one job, and it is the important one: it makes a retry safe. Re-sending with
the same `client_order_id` returns the existing order instead of creating a second.

<Note>
  **It is echoed on the create response and then not stored.** `client_order_id` does
  not come back from `GET /v1/orders` or `GET /v1/orders/{order_id}`, so keep your own
  `client_order_id` → `order_id` mapping. If you lose one, re-sending the identical
  create returns the same order — which is also how you recover its `order_id`.
</Note>

Amends carry no idempotency key at all; see below.

That safe-retry property is what you reach for when a write returns `503 result_unknown`
— the command reached the matching engine but its outcome did not reach you.
**Re-send with the same id, or poll the order. Never re-send with a fresh id.** See
[`result_unknown`](/api-reference/errors#result-unknown); it is the paragraph most
worth reading twice before your bot goes live.

## Amending and canceling

```bash theme={null}
# Amend price and/or quantity
curl -s -X PUT https://api.trysoar.com/v1/orders/{order_id} \
  -H "X-API-KEY: $SOAR_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"price": 55, "qty": 80}'

# Cancel
curl -s -X DELETE https://api.trysoar.com/v1/orders/{order_id} \
  -H "X-API-KEY: $SOAR_API_KEY"
```

An amend body takes `price`, `qty`, or both; an omitted field keeps its current value
and a body with neither is `400`. Any other field you send is ignored.

It takes **no idempotency key** — the `order_id` in the path already names one order.
That also means an amend has no dedup to fall back on, so if one returns
`503 result_unknown`, read the order back before deciding what to do rather than
re-amending blind.

<Warning>
  **Queue position survives only if `price` is unchanged and `qty` does not increase.**
  Sizing down keeps your place. Any price change, *or any increase in size*, makes this
  a new order with a new timestamp and sends it to the back of the queue at that price.

  The size half of that rule is the one that surprises people: adding 50 shares to a
  resting order is not "topping up", it is requoting.
</Warning>

Canceling an order that is already filled, canceled, or expired is
`422 order_not_cancelable`, not something to retry.

<Warning>
  Watch your rate limit around cancels specifically. A throttled cancel leaves an
  order resting that you believe you pulled, and every decision after that is made
  against inventory you do not have.
</Warning>

## Your own orders will not trade with each other

If your incoming order would match your own resting order, neither trades. Both are
reduced by the overlapping quantity, no fill is generated, and no trade prints. An
order entirely consumed this way ends up `canceled`.

This matters if you quote both sides: crossing your own quotes destroys them instead
of filling them. See
[Matching](/concepts/matching-engine#self-trade-prevention).

## After the fill

A fill moves shares and cash immediately. Then:

* `GET /v1/portfolio/fills` — your fill history. Each fill carries `outcome_price`
  (your price) and `trade_price` (the tape's). Reconcile cash against
  **`outcome_price`**; see
  [Binary markets](/concepts/binary-markets#what-you-paid-and-what-printed).
* `GET /v1/portfolio/positions` — what you hold, per outcome.
* `GET /v1/portfolio/balance` — cash, and how much of it is reserved against resting
  orders.

Positions persist until the market resolves, at which point they pay out. See
[Settlement](/concepts/settlement).


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