Skip to content
Skip to main content

Trading API FAQs

Answers to the most common questions about the TradeZero Trading API. For detailed technical documentation, see the relevant reference page linked in each answer.


Orders

Why does my order return 200 OK but orderStatus: "Rejected"?

The API returns 200 OK once the request body is structurally valid. Whether the order is accepted for routing is reflected in orderStatus. Common rejections include:

  • R78 - market order submitted outside Regular Trading Hours (9:30 AM-4:00 PM ET). Use a Limit order with timeInForce: "Day_Plus" or "GTC_Plus" for extended hours.
  • R95 - you tried to open both a long and a short on the same symbol at the same time. Cancel one direction first.
  • R114 - clientOrderId was already used in this session. Generate a fresh unique value for every order - ids are not reusable after cancellation.

Always read orderStatus after a 200 response. See Validation errors.


Can I modify a working order?

There is no modify endpoint. Use the cancel-then-replace pattern: DELETE /v1/api/accounts/{accountId}/orders/{clientOrderId} the original, wait for orderStatus: "Canceled", then POST /order a replacement with a new clientOrderId. Reusing the original id returns R114 even after cancellation - the id is consumed permanently the moment it is first accepted. See Modifying an order.


What's the difference between GET /orders and GET /orders/start-date?

Both are about orders, but they sit at different levels:

  • GET /orders is the today's order book - it returns every order on the account from today's session regardless of current state (rows include New, Accepted, PartiallyFilled, Filled, Canceled, Rejected, DoneForDay, …) plus any still-working orders from previous sessions (multi-day GoodTillCancel / GTC_Plus that haven't filled or expired yet). Each row carries clientOrderId directly, plus order-level fields (orderStatus, executed, priceAvg, limitPrice, timeInForce, …). Use this to manage live orders and to read terminal-state outcomes for orders submitted today.
  • GET /orders/start-date/{date} is the historical orders archive - up to one week of post-trade history. Rows are at the fill level, not the order level: each row is one execution (tradeId, qty, price, commission, totalFees, grossProceeds, netProceeds, tradeDate), and a partial-filled order produces multiple rows. There is no clientOrderId and no orderStatus on these rows. Use this for daily P&L recaps, fee/commission rollups, and reconciliation. Paper accounts have no order history available and always return { "orders": [] }.

Why does canceling an order return 404, 400, or 401?

DELETE /v1/api/accounts/{accountId}/orders/{clientOrderId} has three distinct failure shapes:

  • 401 Unauthorized (JSON) - auth headers were missing. Body: {"statusCode":"Unauthorized","message":"Token not provided","detail":null}.
  • 404 Not found (plain text) — the order was not found (wrong ID, not yet registered, wrong account, auth mismatch, or the order is already terminal such as Filled, Canceled, or Rejected).
  • 400 with JSON body ({"statusCode":"BadRequest","message":"CancelOrderWithResponse","detail":"Unable to fetch account orders..."}) — the {accountId} in the URL path does not match the credentials you authenticated with. For TradeZero America accounts, a common trigger is sending the portal login instead of the 2TZ account number — use the account value from GET /v1/api/accounts.

In all cases, confirm the final state via GET /orders. See Cancel behavior.


Does cancel-all work when there are no open orders?

Yes. DELETE /v1/api/accounts/orders always returns {"message":"Cancel Request Submitted Successfully"} as long as the multipart body carries a valid account field - regardless of whether any orders were open.


Are enum values case-sensitive?

Yes, but enforcement differs by field. side, orderType, securityType, and timeInForce are case-sensitive — wrong case returns 400. For openClose, always send "Open" or "Close" exactly.


Can I send fractional shares?

No. orderQuantity must be a whole integer. Sending 0.5 returns 400 Bad Request with - orderQuantity: Invalid type. Expected: integer, given: number.


What is route: "<no value>" in a rejected order?

Some orders rejected during validation show route: "<no value>" because no venue was selected. Always check orderStatus === "Rejected" and read text for the reason.


Why did my order reject with R54: Unable to reach the destination Route?

The order had no usable route. On a live account, always send an explicit route from GET /routes — omitting route can leave the order with no route assigned and produce R54 during routing. The other cause is sending a route your account doesn't have (route names vary by account, and securityType matters - e.g. a single-leg Option sent through the MLEG-only PAPERM route rejects with R54).

Fix: query GET /v1/api/accounts/{accountId}/routes, pick a routeName whose securityTypes matches your order, and send it explicitly in the route field. Query /routes per account and use the returned routeName — route availability varies by subscription. See Get available routes.


Which routes are available - and what's the difference between paper and live?

Query GET /v1/api/accounts/{accountId}/routes:

  • Paper accounts return two synthetic routes - PAPER (Stock + Option) with Day, GoodTillCancel, GoodTillCrossing and PAPERM (MLEG, Day-only).
  • Live accounts typically return five real routes - SMART (smart router for Stocks; widest order-type and TIF set), CTDL (direct-access stock route), SMARTO (smart router for single-leg Options), SMARTM (smart router for multi-leg Options), and ARCA (direct ECN route for Stocks; adds IOC and FOK TIFs).

The exact set on a given live account depends on subscription. Always query /routes and use what the API returns for your account. See Get available routes for the field-level breakdown.


Can I cancel an order immediately after placing it?

A DELETE /v1/api/accounts/{accountId}/orders/{clientOrderId} issued before the order is registered may return 404 Not found. Poll GET /orders until the order appears before canceling, or retry the cancel after a brief delay.


Accounts & Authentication

Which account ID do I use for API calls? My TradeZero America account shows two IDs.

Use the 2TZ-prefixed account number, not your login. TradeZero America (TZA) accounts have both a login (e.g. ZKA47093) and an account number shown as "Account" (e.g. 2TZ35844); only the 2TZ account number is valid for API requests. The safest approach is to call GET /v1/api/accounts and use the account value it returns for every {accountId} path parameter and request body. Sending the login instead fails account resolution (404, or the "Account for User was not found, or User doesn't have entitlements." error on locate and order calls). Most other TradeZero accounts have a single ID. See Account Information.


How do I tell paper accounts from live accounts in the API response?

Read the accountType field from GET /v1/api/account/{accountId}. Paper accounts return "Paper"; live accounts return "Live" (or another non-"Paper" value reflecting how the account is classified). Do not pattern-match account IDs - use the server-issued field. See Account Types.


Why am I getting 404 Not found instead of 401 Unauthorized when my credentials are wrong?

Most read endpoints return 404 Not found when credentials are missing or invalid. The order-cancel endpoint (DELETE /v1/api/accounts/{accountId}/orders/{clientOrderId}) returns 401 Unauthorized instead. Handle both status codes in your client. See Error handling.


Do I need to refresh or rotate my API keys?

No automatic expiry - keys stay valid until you explicitly regenerate or disable them in the portal. Rotate if you suspect a leak: use Regenerate Secret to replace just the secret (invalidating the old one immediately) or Disable + Generate for a full key replacement. Every lifecycle event is logged in the portal's Audit Trail and triggers an email. See Key lifecycle rules.


Short Locates

Why does POST /quote return 200 OK but no offer ever appears in /history?

On paper accounts, /quote returns 200 OK but the quote settles in /history as locateStatus: 56 Rejected because locates run on live accounts only. Use a funded live account for the full quote → accept → inventory workflow. See Account Types.

On live accounts, quotes that find no available inventory may produce a Rejected row with text: "No shares available." rather than an Offered row.


How long does an offered locate quote stay valid?

30 seconds from when the row first reaches locateStatus: 65 (Offered). If you don't POST /accept within that window, both rows (and the .SU sibling for Reg SHO symbols) move to status 67 (Expired) and you need to issue a fresh /quote.


What is a Reg SHO threshold security and why do I see two rows in /history?

A Reg SHO threshold security is an equity security that appears on a threshold securities list under Regulation SHO due to persistent fail-to-deliver positions meeting regulatory thresholds for five consecutive settlement days. The platform returns two priced rows for these symbols: one for the Pre-Borrow offer (your original quoteReqID) and one for the cheaper Single Use offer (quoteReqID + ".SU"). Accept either one - accepting the other auto-expires. See Short Locates.


WebSocket

Do the streams send a keepalive ping?

No. The server does not send a keepalive. Implement your own reconnection logic with exponential backoff so long-lived connections can recover automatically if they drop. See Reconnection.

Are both streams available on paper accounts?

Yes. The P&L and Portfolio streams work identically on paper and live accounts. P&L updates are driven by simulated quote ticks on your paper positions. See WebSocket API.