API Conventions
This page collects rules that apply across the Trading API, WebSocket streams, and MCP tools. Narrative guides link here instead of redefining the same enums, field names, and retry semantics on every page.
HTTP status, business outcomes, and content types
HTTP status is not always the business outcome. Order and locate writes may return HTTP 200 while the business result is still pending or rejected - read domain fields (orderStatus, text, locate history rows) rather than treating 2xx as success. Cancel all orders returns 200 when the cancel request is accepted, not when every order has reached Canceled.
| Situation | HTTP status | Body |
|---|---|---|
| JSON order/locate accepted for routing | 200 | JSON - may be PendingNew before final rejection |
| Business rejection (order) | 200 | JSON with orderStatus: "Rejected" |
| Schema / validation failure (order) | 400 | text/plain |
| Account mismatch on write | 400 | JSON { statusCode, message, detail } |
| Missing/invalid auth on many reads | 404 | text/plain |
| Missing auth on cancel-by-id | 401 | JSON |
| Rate limit | 429 | empty |
| Cancel all (wrong encoding) | 404 | text/plain - JSON body not accepted |
REST endpoints use application/json. Exceptions documented in the guides include DELETE /accounts/orders (form-encoded or multipart only) and 400 validation errors on order placement (text/plain). Branch on Content-Type before parsing. See Equity Trading → Error handling.
There is no modify endpoint - change a working order with cancel-then-replace and a new clientOrderId. See Trading API FAQs and Modifying an order.
Environments (paper and live)
Paper and live use the same host - https://webapi.tradezero.com for REST and wss://webapi.tradezero.com/stream/... for WebSocket. The API key pair in your headers selects the environment. There is no separate paper hostname today.
Treat environment selection as a client-side safety requirement, not an optional convenience:
- After authentication, read
accountTypefromGET /v1/api/account/{accountId}("Paper"vs anything else). - Block write operations when the environment does not match what the user selected in your UI.
- Never store paper and live keys in the same configuration slot without an explicit switch.
See Authentication → Pick an environment and Paper vs live differences below.
async function assertEnvironment(
accountId: string,
expected: 'paper' | 'live',
): Promise<void> {
const detail = await getAccount(accountId);
const isPaper = detail.accountType === 'Paper';
if (expected === 'paper' && !isPaper) throw new Error('Live account - writes blocked');
if (expected === 'live' && isPaper) throw new Error('Paper account - live trading blocked');
}
clientOrderId: deduplication, not idempotency
clientOrderId is your session-scoped deduplication key, not a Stripe-style idempotency key. The server rejects a second POST /order with the same value (R114) even when the first order was canceled or rejected. It does not replay the original response on retry.
Safe retry after a transport failure
- Generate
clientOrderIdbefore the firstPOST /orderand persist it locally. - On timeout or ambiguous
5xx, do not POST again with the same id. - Poll
GET /v1/api/accounts/{accountId}/order/{clientOrderId}(retry404for ~1–2 s - registration can lag). - If the order exists, reconcile against
orderStatus,executed, andleavesQuantity. - If the order truly does not exist after polling, submit a new order with a fresh
clientOrderId.
See Equity Trading → Order identity and the Dedup and safe retry recipe.
Timestamps and time zones
| Rule | Detail |
|---|---|
| Time zone | Timestamps are UTC. |
| Format | ISO-8601 strings on REST and WebSocket (2026-05-14T15:36:36.987036Z, ...+00:00, or date-only T00:00:00 on some historical rows). |
| Fractional seconds | Precision varies (3–7 digits). Parse as ISO-8601; do not assume a fixed width. |
| Session times | Regular trading hours and extended-hours rules are stated in Eastern Time (ET) on Equity Trading → Order types, times in force, and session hours. |
Some historical fill rows expose bare local times such as execTime: "06:10:12" without a zone. Treat those as exchange-local display values and prefer tradeDate / startTime / lastUpdated for programmatic sorting.
Numeric precision
- Prices -
limitPriceaccepts up to four decimal places; venues enforce tick size (Reg NMS: $0.01 at or above $1.00, $0.0001 below). See Equity Trading → Place an order. - Quantities - whole shares only on
orderQuantityand locatequantity. - P&L - account-level aggregates and per-lot
pnl[]rows may round differently. Do not expect penny-perfect reconciliation between/pnl,/positions, and summed fill history without your own rounding policy.
Wire naming and casing
The API uses different names on requests, REST responses, and WebSocket payloads. Always branch on the surface you are parsing.
| Concept | Request / REST | WebSocket / alternate |
|---|---|---|
| Buying power | buyingPower on GET /accounts list rows | bp on GET /account/{accountId} detail |
| Account id in subscribe body | accountId (REST paths) | "account" on P&L / Portfolio subscribe |
| Client order id | clientOrderId | userOrderId = "{accountId}:{clientOrderId}" |
| Stop price | stopPrice on POST /order | priceStop on order rows |
| Locate correlation id | quoteReqId on quote/accept | quoteReqID in some locate history rows |
| Multi-leg security type | Send "Mleg" on POST /order | /routes may advertise "MLEG"; Portfolio stream may show "MLEG" |
| Position identifier | positionId (string on open positions) | id on Portfolio stream position events |
| Canceled quantity spelling | canceledQuantity (REST) | cancelledQuantity (some WebSocket order shapes) |
| Last fill quantity | lastQuantity (REST) | lastQty (WebSocket) |
Send "Mleg" (not "MLEG") on POST /order even when /routes lists "MLEG". See Options Trading and Order shape across REST and WebSocket.
positionId wire types
- Open positions (
GET /positions,/pnl, P&L stream) -positionIdis a string (numeric-looking; do not parse as an integer). - Closed positions (
GET /positions/closed) -positionIdarrives as a JSON number on the wire. Hold it as a string in application code when values exceedNumber.MAX_SAFE_INTEGER. See Closed Positions →positionIdhandling.
GET /routes vs POST /order
GET /routes advertises routing destinations and may list orderTypes beyond the placeable set. POST /order accepts only "Market", "Limit", "Stop", and "StopLimit". Use /routes for destination and time-in-force discovery; send one of the four supported order types when placing orders. Canonical SMART session rules: Equity Trading → Order types, times in force, and session hours. See also Equity Trading → Get available routes.
Order and margin fields on responses
Several response fields are reserved for account/order context and return 0 or empty on order rows:
marginRequirement/maintenanceRequirementon order rows -0; for pre-trade margin gates use accountbp,marginDeficit, andmaintenanceDeficit.marginRequirementonGET /account/{accountId}- present on some accounts; usebp,marginDeficit, andmaintenanceDeficitfor trading gates.
pnl[] vs /positions
GET /pnl returns per-lot profit and loss but does not include share quantity or long/short side. Join to Open Positions on positionId (then tradedSymbol, then symbol) when you need size or side. Rows with exposure: 0 represent lots closed earlier in the session.
Paper vs live differences
| Topic | Paper | Live |
|---|---|---|
| Host | webapi.tradezero.com | Same |
| Credentials | Paper key pair | Live key pair |
| Locates | Endpoints accept requests; no offerable inventory | Full quote → accept → inventory workflow |
| Historical order archive | Empty ({ "orders": [] }) | Up to one week (fills) / paginated history |
| ETB / short simulation | All symbols treated as easy-to-borrow | Real ETB / HTB inventory |
| Routes when omitted | Auto PAPER / PAPERM | Send explicit route from /routes |
| AtTheOpening / IOC / FOK | IOC / FOK not available. Paper can accept AtTheOpening more broadly during RTH than live SMART, so do not use paper to validate OPG eligibility. | Supported where route allows - see Paper vs live |
| Async rejection messages | Paper-session notes in text; live R-codes on live | Full routing and R-code behavior |
| WebSocket streams | Available (beta) | Available (beta) |
Rejection codes
The canonical R-code table lives on Order Rejections. Integration timing, polling, and WebSocket notes remain on Equity Trading → Order rejections.
Rate limits
Per-endpoint limits are per API key pair (not a single global counter). The Rate Limits page lists sustained rates and burst capacity. Integrate against the endpoint table and treat 429 with backoff - the API does not emit Retry-After or X-RateLimit-* headers.
Real-time updates
Order and P&L changes can be asynchronous on live routed orders. Use the Portfolio and P&L WebSocket streams (beta) or poll REST for updates. See About WebSocket API.
Official integrations
TradeZero integrations use HTTPS with TZ-API-KEY-ID and TZ-API-SECRET-KEY. Personal assistants use TradeZero MCP with OAuth sign-in. Community samples and recipes on this site show Python and TypeScript examples.
Third-party packages that script a browser session (for example screen-scraper clients) are not affiliated with TradeZero and are not substitutes for this API. Use only credentials and endpoints documented on this site.
Versioning
Routes are prefixed with /v1/. Breaking changes to production endpoints are announced in the Change Log. Deprecated request values (such as legacy locate type strings) may continue to be accepted without a published sunset date until removal is announced - watch the Change Log before relying on deprecated fields in production.