Live trading is paper-trading today's session against the real-time market. You open a session on a live_sim account and use the same session-scoped order, position, and transaction endpoints that practice sessions use — only the path prefix differs (/accounts/{id}/sessions/{sid}/… here, /practice/sessions/{sid}/… for a trading day). Live sessions add dry-run and cancel/replace (PUT), which practice does not have. See Live trading for the concepts and Accounts & sessions for the full account/session API. Requires a registered account.
Open a live session
Create a live_sim account once, then open today's session on it with an empty body:
# create the account (one-time)
curl -s https://api.0dtespx.com/accounts \
-X POST -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Live paper","engine":"live_sim"}'
# open today's session
curl -s https://api.0dtespx.com/accounts/$ACCT/sessions \
-X POST -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{}'
The session opens at the latest tick with the account's carried cash balance (you don't pass a starting capital — that's the account's). It is gated on market hours and fresh market data: outside those, you get 400. Re-requesting today's session while it's open returns the open session (200). Find the open session any time with GET /accounts/{id}/sessions/current (or null when none is open).
The runnable blocks on this page deliberately stop short of opening a session — that's the one call whose outcome depends on the wall clock, and starting a live day by accident from a docs page is not a favour. Point them at an account you already own instead: outside regular trading hours /sessions/current simply answers null, the session-scoped reads have no id to fill in, and the errors you see are the API's own — which is exactly what your client will have to handle.
GET /accounts
Lists your accounts and saves the first one's id as {{account_id}}. Paste a different one into the blocks below if it isn't the live_sim account you trade.
Interactive — run this request from the docs
GET /accounts/{{account_id}}/sessions/current
The open live session, or null. Outside market hours there is nothing to capture — {{session_id}} stays empty and the reads below stay disabled.
Interactive — run this request from the docs
Session-scoped order endpoints
These are the live-account paths — the same order/position/transaction shapes as a practice session, plus dry-run and cancel/replace:
| Method | Path | Purpose |
|---|---|---|
POST |
/accounts/{id}/sessions/{sid}/orders |
Place an order |
POST |
/accounts/{id}/sessions/{sid}/orders/dry-run |
Validate + preview without persisting |
PUT |
/accounts/{id}/sessions/{sid}/orders/{orderId} |
Atomically cancel + replace a resting limit order |
DELETE |
/accounts/{id}/sessions/{sid}/orders/{orderId} |
Request cancellation of a working order |
GET |
/accounts/{id}/sessions/{sid}/orders · …/orders/{orderId} |
List / get orders |
GET |
/accounts/{id}/sessions/{sid}/positions |
Current positions |
GET |
/accounts/{id}/sessions/{sid}/transactions |
Transaction log |
GET |
/accounts/{id}/sessions/{sid}/history |
Per-second P&L history (chart line) |
Place an order
The request body matches the orders API. On a live session, two additions make retries safe:
curl -s https://api.0dtespx.com/accounts/$ACCT/sessions/$SID/orders \
-X POST -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-H 'Idempotency-Key: ord-2025-05-07-001' \
-d '{
"type":"limit","price":"5.00","price_effect":"debit",
"legs":[{"instrument":"SPXW 260507C05950000","quantity":"1","action":"buy to open"}]
}'
POST /accounts/{{account_id}}/sessions/{{session_id}}/orders/dry-run
Runs every validation and returns the same response without persisting anything — the safe way to rehearse a live order. Point the leg at TODAY's SPXW expiry and a strike that exists, or it fails validation.
Interactive — run this request from the docs
POST /accounts/{{account_id}}/sessions/{{session_id}}/orders
This places a REAL order in your open live session — it can fill against the live market. Dry-run it first, and edit the leg to today's expiry. The Idempotency-Key is held across a 504 or 409 so a retry can't double-place.
Interactive — run this request from the docs
Idempotency-Key(header, optional but recommended) — scoped per session. A duplicate submission within the retention window returns the original response instead of placing a second order. The same key under a different account/session mints a distinct order. This is also how you resolve a504 outcome_unknown: retry under the same key until it returns a terminal outcome (a409 duplicate_idempotency_keywhile the original is still in flight means keep retrying).client_order_id(body, optional UUID) — used as the order's id; a replay with the same id resolves to the existing order. When omitted, one is derived from the idempotency key, so most clients never need to set it.
Live orders enforce the same trading rules as practice, at the exchange:
- 0DTE SPX index options only. Every leg must be a 0DTE cash-settled SPX index option; an equity, ETF, non-SPX, or non-0DTE leg is rejected with
400 only 0DTE SPX index options can be traded(and the more specificonly SPX index options can be traded …/only 0DTE options …). - Defined-risk only — no naked shorts. An order whose resulting position would hold an uncovered short option is rejected with
400 naked short positions are not allowed, regardless of capital, checked before the buying-power gate. Cover the short with a long of the same type to make it a spread. Unlike practice rejections (JSON{"message": …}), the exchange relays live rejection text as a plain-text body — the message string is the same.
Order lifecycle
Live orders never time-travel. status is the persisted state, it only moves forward along one ladder, and it stays terminal once set:
pending → routed → live → {filled, canceled, expired, rejected}
| Status | Meaning |
|---|---|
pending |
We accepted the order; nothing downstream has confirmed it yet. Transient — a fraction of a second today |
routed |
Confirmed by the next hop, not on a book yet. Reserved — nothing emits it today; handle it now and it costs you nothing later |
live |
Resting on the book, awaiting a matching tick |
filled |
Executed |
canceled |
Your cancel reached the book while the order was still resting |
expired |
Auto-expired at the 4:00 PM ET close |
rejected |
Failed a buying-power recheck at fill time, or never reached the book (rejection_reason explains) |
A rung can be skipped — an order can go from pending straight to a terminal — but the sequence never runs backward, and the first terminal recorded is final.
The three non-terminal states are all working: they reserve buying power, they count toward your open orders, and a cancel is accepted on any of them. Only live accepts a replace.
The POST response already carries live for a resting order, because the API waits for the exchange. pending is what you see on the order_update stream, which emits the order at admission and again at acceptance — that is where the progression is visible.
cancel_requested: true is a separate flag, not a status: see Cancel and replace.
No orders are accepted after the close (400).
Cancel and replace
# request cancellation of a working order
curl -s -X DELETE https://api.0dtespx.com/accounts/$ACCT/sessions/$SID/orders/$ORDER_ID -H "Authorization: $TOKEN"
# atomically cancel + replace a resting (live) LIMIT order's price
curl -s -X PUT https://api.0dtespx.com/accounts/$ACCT/sessions/$SID/orders/$ORDER_ID \
-H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{"price":"5.50","price_effect":"debit"}'
Cancel is a request, and best-effort. It is accepted in every non-terminal state — flattening risk must never depend on where the order sits on the ladder — and only an already-terminal order returns 409. The 204 means the cancel was accepted and queued behind whatever is already in flight for that order, not that the order is gone. The order then carries cancel_requested: true (show it as "canceling…") until it resolves one of three ways: it was resting → canceled; it never reached the book → rejected, cancel a no-op; it filled first → filled, with cancel_requested still true. That last case is irreducible — any exchange can match in the moment before a cancel lands — so read the terminal status, not the 204, as the outcome.
Replace requires live. Only price and price_effect may change; type, legs, and underlying are fixed. An order still pending or routed has no resting price to revise and returns 409 order is not yet live — retry in a moment, or cancel instead. An order already carrying cancel_requested returns 409 order is being canceled; a terminal one returns 409 order is no longer working. Replacing a non-limit order returns 400.
Read your state
curl -s https://api.0dtespx.com/accounts/$ACCT/sessions/current -H "Authorization: $TOKEN" # the open session
curl -s https://api.0dtespx.com/accounts/$ACCT/sessions/$SID/orders -H "Authorization: $TOKEN" # orders
curl -s https://api.0dtespx.com/accounts/$ACCT/sessions/$SID/positions -H "Authorization: $TOKEN" # positions
curl -s https://api.0dtespx.com/accounts/$ACCT/sessions/$SID/history -H "Authorization: $TOKEN" # per-second P&L history
curl -s https://api.0dtespx.com/accounts/$ACCT/sessions/$SID/transactions -H "Authorization: $TOKEN" # transactions
GET /accounts/{{account_id}}/sessions/{{session_id}}/orders
Every order in today's session, with its persisted lifecycle status.
Interactive — run this request from the docs
GET /accounts/{{account_id}}/sessions/{{session_id}}/positions
The session's open positions, marked against the latest processed tick — the ?at= cursor is ignored while the day is open.
Interactive — run this request from the docs
GET /accounts/{{account_id}}/sessions/{{session_id}}/transactions
The day's fills, plus the settlement entries once the close has been processed.
Interactive — run this request from the docs
GET /accounts/{{account_id}}/sessions/{{session_id}}/history
The per-second P&L line behind the chart. Switch the interval on to down-sample a full day into something readable.
Interactive — run this request from the docs
Live state also pushes over the WebSocket — REST is authoritative for an operation you just performed; the WebSocket is the source of truth for changes you didn't initiate (other tabs, fills, the close). De-dupe orders by id and prefer the newer updated_at. After a disconnect, refetch positions/orders/transactions to recover.
Settlement and ending
Settlement runs automatically once the closing tick arrives; the session's status becomes settled, its closing balance carries into the account's cash_balance, and GET /accounts/{id}/sessions/current returns null. Once started, a live session can't be stopped — it runs to the close and settles there.
Error semantics
| Status | Meaning |
|---|---|
400 |
Validation error — market closed, non-0DTE-SPX leg, naked short, insufficient quantity to close, or insufficient buying power. Rejections relayed from the exchange are plain-text bodies; validation the API catches first keeps the JSON envelope — handle both. |
409 |
The order's state forbids the operation — already terminal, not yet live (replace only), or already being canceled. On a retried Idempotency-Key while the original request is still in flight it is duplicate_idempotency_key instead: keep retrying the same key until it resolves. |
503 |
Live trading briefly unavailable — always safe to retry; no state changed. |
504 |
outcome_unknown — the exchange couldn't confirm within the deadline whether the request applied, so it may or may not have. Resolve it: retry under the same Idempotency-Key (the retry returns the original outcome — a 409 while it's still in flight means keep retrying), or reconcile with a GET on the order/session. |