Skip to content
Skip to main content

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.

SituationHTTP statusBody
JSON order/locate accepted for routing200JSON - may be PendingNew before final rejection
Business rejection (order)200JSON with orderStatus: "Rejected"
Schema / validation failure (order)400text/plain
Account mismatch on write400JSON { statusCode, message, detail }
Missing/invalid auth on many reads404text/plain
Missing auth on cancel-by-id401JSON
Rate limit429empty
Cancel all (wrong encoding)404text/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:

  1. After authentication, read accountType from GET /v1/api/account/{accountId} ("Paper" vs anything else).
  2. Block write operations when the environment does not match what the user selected in your UI.
  3. 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

  1. Generate clientOrderId before the first POST /order and persist it locally.
  2. On timeout or ambiguous 5xx, do not POST again with the same id.
  3. Poll GET /v1/api/accounts/{accountId}/order/{clientOrderId} (retry 404 for ~1–2 s - registration can lag).
  4. If the order exists, reconcile against orderStatus, executed, and leavesQuantity.
  5. 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

RuleDetail
Time zoneTimestamps are UTC.
FormatISO-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 secondsPrecision varies (3–7 digits). Parse as ISO-8601; do not assume a fixed width.
Session timesRegular 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 - limitPrice accepts 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 orderQuantity and locate quantity.
  • 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.

ConceptRequest / RESTWebSocket / alternate
Buying powerbuyingPower on GET /accounts list rowsbp on GET /account/{accountId} detail
Account id in subscribe bodyaccountId (REST paths)"account" on P&L / Portfolio subscribe
Client order idclientOrderIduserOrderId = "{accountId}:{clientOrderId}"
Stop pricestopPrice on POST /orderpriceStop on order rows
Locate correlation idquoteReqId on quote/acceptquoteReqID in some locate history rows
Multi-leg security typeSend "Mleg" on POST /order/routes may advertise "MLEG"; Portfolio stream may show "MLEG"
Position identifierpositionId (string on open positions)id on Portfolio stream position events
Canceled quantity spellingcanceledQuantity (REST)cancelledQuantity (some WebSocket order shapes)
Last fill quantitylastQuantity (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) - positionId is a string (numeric-looking; do not parse as an integer).
  • Closed positions (GET /positions/closed) - positionId arrives as a JSON number on the wire. Hold it as a string in application code when values exceed Number.MAX_SAFE_INTEGER. See Closed Positions → positionId handling.

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 / maintenanceRequirement on order rows - 0; for pre-trade margin gates use account bp, marginDeficit, and maintenanceDeficit.
  • marginRequirement on GET /account/{accountId} - present on some accounts; use bp, marginDeficit, and maintenanceDeficit for 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

TopicPaperLive
Hostwebapi.tradezero.comSame
CredentialsPaper key pairLive key pair
LocatesEndpoints accept requests; no offerable inventoryFull quote → accept → inventory workflow
Historical order archiveEmpty ({ "orders": [] })Up to one week (fills) / paginated history
ETB / short simulationAll symbols treated as easy-to-borrowReal ETB / HTB inventory
Routes when omittedAuto PAPER / PAPERMSend explicit route from /routes
AtTheOpening / IOC / FOKIOC / 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 messagesPaper-session notes in text; live R-codes on liveFull routing and R-code behavior
WebSocket streamsAvailable (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.