Rate Limits
To protect platform stability, the TradeZero Trading API enforces per-endpoint rate limits on https://webapi.tradezero.com. Limits apply per API key pair - all requests authenticated with the same key share the same buckets. When a bucket is exhausted, the server returns 429 Too Many Requests with an empty body.
Ignore aggregate marketing budgets. Integrate against the endpoint table below - each route has its own sustained rate and burst capacity. The API does not emit Retry-After or X-RateLimit-* headers on 429 responses.
At a glance
| Behavior | What you will see |
|---|---|
| Scope | Per API key (your key pair has its own buckets) |
| Algorithm | Token bucket per route - sustained refill rate plus a burst allowance |
| Throttle signal | HTTP 429 Too Many Requests |
| Response body | Empty |
| Rate-limit headers | No Retry-After or X-RateLimit-* headers on 429 responses |
| Recovery | Pause briefly, then retry - retry after 2 seconds |
| Global cap | No cluster-wide request cap - only the per-key limits apply |
How rate limiting works
Each endpoint has its own token bucket:
- Sustained rate - the number of requests allowed per 1-second window when traffic is spread evenly.
- Burst capacity - how many requests can land instantly before the per-second limit applies. When burst capacity equals the sustained rate, short parallel spikes above the sustained rate trigger
429immediately. When burst capacity is 1, there is effectively no burst buffer - any request above the sustained rate is rejected right away. - Per-endpoint buckets - throttling on
GET /positionsdoes not consume tokens onGET /orders.
Paper and live share the same host and limit model; only credentials differ. Locates endpoints require a live account.
Hosted TradeZero MCP tool calls count against the same endpoint rate-limit buckets as the underlying REST operations.
Accounts and cash
GET /v1/api/accounts, GET /v1/api/account/{accountId}, and cash ledger pages share the per-key token-bucket model and return 429 when exhausted. Space dashboard refreshes (≥1 s between polls) and handle 429 with backoff. Cash pagination under burst load is documented on Cash Transactions. Open, closed, and historical positions use the 3/s limit in the Positions table below.
Complete endpoint reference
Limit = requests allowed per second. Burst = how many requests can arrive at once before the per-second limit applies.
Orders
| Method | Path | Limit | Burst |
|---|---|---|---|
POST | /v1/api/accounts/{accountId}/order | 10/s | 10 |
DELETE | /v1/api/accounts/{accountId}/orders/{clientOrderId} | 15/s | 15 |
DELETE | /v1/api/accounts/orders | 3/s | 1 (no burst) |
GET | /v1/api/accounts/{accountId}/is-easy-to-borrow/symbol/{symbol} | 2/s | 2 |
GET | /v1/api/accounts/{accountId}/order/{clientOrderId} | 2/s | 2 |
GET | /v1/api/accounts/{accountId}/orders | 2/s | 2 |
GET | /v1/api/accounts/{accountId}/orders-with-pagination/start-date/{date} | 1/s | 1 |
GET | /v1/api/accounts/{accountId}/orders/start-date/{date} | 1/s | 1 |
GET | /v1/api/accounts/{accountId}/routes | 1/s | 1 |
Positions
| Method | Path | Limit | Burst |
|---|---|---|---|
GET | /v1/api/accounts/{accountId}/positions | 3/s | 3 |
GET | /v1/api/accounts/{accountId}/pnl | 3/s | 3 |
GET | /v1/api/accounts/{accountId}/positions/closed | 3/s | 3 |
GET | /v1/api/accounts/{accountId}/positions/historical | 3/s | 3 |
Locates (live account only)
| Method | Path | Limit | Burst |
|---|---|---|---|
POST | /v1/api/accounts/locates/quote | 1/s | 1 |
POST | /v1/api/accounts/locates/accept | 10/s | 10 |
DELETE | /v1/api/accounts/locates/cancel/accounts/{accountId}/quoteReqID/{id} | 2/s | 2 |
POST | /v1/api/accounts/locates/sell | 10/s | 10 |
GET | /v1/api/accounts/{accountId}/locates/history | 2/s | 2 |
GET | /v1/api/accounts/{accountId}/locates/inventory | 2/s | 2 |
Locate quote - 30-second same-symbol rule (separate from 429)
In addition to the 1/s API limit, the locate service enforces a 30-second cooldown per symbol per account: wait at least 30 seconds between POST /locates/quote calls for the same symbol on the same account.
How it behaves:
- Two quotes for the same symbol within the cooldown both return
200 OKsynchronously - the request is accepted and queued. - The cooldown is not expressed as
429at the HTTP layer. Rejection or deduplication surfaces asynchronously inGET /locates/history(for examplelocateStatus: 56with a business reason), not as an immediate throttle response. - Treat 30 seconds per symbol per account as a hard client-side spacing rule regardless of the synchronous status code.
Use distinct symbols when you need to issue multiple quotes in a short window. See Short Locates for the full lifecycle.
Out of scope
| Surface | Notes |
|---|---|
Stock Scanner (api.tradezero.com) | Separate host and limit surface |
| WebSocket streams | Long-lived connections - not REST request buckets. See WebSocket API |
HTTP 429 response
When throttled:
HTTP/1.1 429 Too Many Requests
Content-Length: 0
Every 429 response:
- Status:
429 Too Many Requests - Body: empty (no JSON error envelope)
- Headers: no
Retry-After,X-RateLimit-Limit,X-RateLimit-Remaining, orX-RateLimit-Reset
A 429 is transient - slow down and retry; it is not a permanent ban.
Client integration patterns
After a 429
- Stop the current burst or polling loop for that endpoint.
- Wait at least 1–2 seconds before retrying.
- Retry once with backoff; if
429persists, use exponential backoff with jitter (2 s → 4 s → 8 s, cap at 60 s). - Do not retry in a tight loop on the same endpoint.
Order writes
| Endpoint | Safe pattern |
|---|---|
POST /order | Serial queue with ~100 ms spacing for sustained scripted usage; stay within the 10/s limit |
DELETE /orders/{id} | Space cancels to stay within the 15/s limit |
DELETE /accounts/orders | Max 3/s, no burst - never fire cancel-all in a tight loop |
Portfolio and order reads
| Endpoint | Minimum spacing | Recommended cadence |
|---|---|---|
GET /orders, GET /order/{id}, ETB | ~1 s between calls | Poll until terminal order status, then back off |
GET /orders/start-date/{date}, paginated history, GET /routes | ~1 s | On-demand or low-frequency refresh |
GET /positions | ~2 s sustained; avoid >3 parallel | 5 s background refresh |
GET /pnl | ~2 s sustained | 5 s background refresh |
Prefer WebSocket streams for sub-second order and P&L updates instead of sub-second REST polling.
Locate workflows at volume
| Rule | Guidance |
|---|---|
/quote | Max 1 quote/s; no burst |
| Same symbol | Wait ≥ 30 s between quotes for the same symbol on the same account |
| Accept / sell / cancel | Respect 10/s (accept, sell) and 2/s (cancel, history, inventory) |
| Confirmation | POST/DELETE return 200 when queued - confirm state in /locates/history |
Multiple processes or services
Your account has one API key pair. Every REST call authenticated with it - whether from one script, several microservices, or multiple developers - draws from the same per-endpoint buckets. If several processes poll or place orders at once, their combined rate counts against your limits. Coordinate request timing across your integration: use a single polling loop, serialize writes through one queue, and prefer WebSocket streams over parallel REST polling where you need frequent updates.
FAQ
Per API key, not per IP. Your account has one API key pair; all requests made with it share that key's buckets regardless of which machine or process sends them.
Pause, wait 1–2 seconds, retry with backoff. Do not immediately re-fire the same parallel burst.
No. WebSocket connections use a separate model. REST limits apply only to HTTP requests on webapi.tradezero.com.
Same host and limit model for shared endpoints. Locates are live-only.
The API acknowledges quotes with 200 before locate business rules are applied. The 30-second same-symbol cooldown and inventory checks surface in /locates/history, not as 429.
DELETE /accounts/orders has burst capacity 1 - only ~3 requests per second with no parallel buffer. Single-order cancels allow a larger burst (15/s configured).
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
429 on GET /orders or /positions | Polling faster than the limit | Slow to ≥1–2 s between reads; serialize parallel fetches |
429 only in production | Several processes polling or writing in parallel | Coordinate into one polling loop or request queue; slow combined traffic to the limits in this doc |
429 after parallel POST /order | Burst of concurrent order writes | Queue writes; stay within the 10/s limit |
Second locate quote returns 200 but does not appear in inventory | 30 s same-symbol business rule | Wait 30 s; check /locates/history for locateStatus |
Empty 429 body | Expected | Back off; do not parse JSON from the body |
For authentication, see Authentication. For order and locate semantics, see Equity Trading, Positions & P&L, and Short Locates.