Skip to content
Skip to main content

API Rate Limits

The TradeZero Trading API enforces per-endpoint request limits on https://webapi.tradezero.com. Limits are applied per API key — your account has one API key pair, and all requests authenticated with it share the same buckets. Exceeding a limit returns 429 Too Many Requests.

At a glance

BehaviorWhat integrators see
ScopePer API key (your key pair has its own buckets)
AlgorithmToken bucket per route — sustained refill rate plus a burst allowance
Throttle signalHTTP 429 Too Many Requests
Response bodyEmpty
Rate-limit headersNo Retry-After or X-RateLimit-* headers on 429 responses
RecoveryPause briefly, then retry — a retry after ~2 seconds succeeds
Global capNo 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 429 immediately. 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 /positions does not consume tokens on GET /orders.

Paper and live share the same host and limit model; only credentials differ. Locates endpoints require a live account.

Complete endpoint reference

Limit = requests allowed per second. Burst = how many requests can arrive at once before the per-second limit applies.

Orders

MethodPathLimitBurst
POST/v1/api/accounts/{accountId}/order10/s10
DELETE/v1/api/accounts/{accountId}/orders/{clientOrderId}15/s15
DELETE/v1/api/accounts/orders3/s1 (no burst)
GET/v1/api/accounts/{accountId}/is-easy-to-borrow/symbol/{symbol}2/s2
GET/v1/api/accounts/{accountId}/order/{orderId}2/s2
GET/v1/api/accounts/{accountId}/orders2/s2
GET/v1/api/accounts/{accountId}/orders-with-pagination/start-date/{date}1/s1
GET/v1/api/accounts/{accountId}/orders/start-date/{date}1/s1
GET/v1/api/accounts/{accountId}/routes1/s1

Positions

MethodPathLimitBurst
GET/v1/api/accounts/{accountId}/positions3/s3
GET/v1/api/accounts/{accountId}/pnl3/s3

Locates (live account only)

MethodPathLimitBurst
POST/v1/api/accounts/locates/quote1/s1
POST/v1/api/accounts/locates/accept10/s10
DELETE/v1/api/accounts/locates/cancel/accounts/{accountId}/quoteReqID/{id}2/s2
POST/v1/api/accounts/locates/sell10/s10
GET/v1/api/accounts/{accountId}/locates/history2/s2
GET/v1/api/accounts/{accountId}/locates/inventory2/s2

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 OK synchronously — the request is accepted and queued.
  • The cooldown is not expressed as 429 at the HTTP layer. Rejection or deduplication surfaces asynchronously in GET /locates/history (for example locateStatus: 56 with 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

SurfaceNotes
Stock Scanner (api.tradezero.com)Separate host and limit surface
WebSocket streamsLong-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, or X-RateLimit-Reset

A 429 is transient — slow down and retry; it is not a permanent ban.

Client integration patterns

After a 429

  1. Stop the current burst or polling loop for that endpoint.
  2. Wait at least 1–2 seconds before retrying.
  3. Retry once with backoff; if 429 persists, use exponential backoff with jitter (2 s → 4 s → 8 s, cap at 60 s).
  4. Do not retry in a tight loop on the same endpoint.

Order writes

EndpointSafe pattern
POST /orderSerial queue with ~100 ms spacing for sustained automation; stay within the 10/s limit
DELETE /orders/{id}Space cancels to stay within the 15/s limit
DELETE /accounts/ordersMax 3/s, no burst — never fire cancel-all in a tight loop

Portfolio and order reads

EndpointMinimum spacingRecommended cadence
GET /orders, GET /order/{id}, ETB~1 s between callsPoll until terminal order status, then back off
GET /orders/start-date/{date}, paginated history, GET /routes~1 sOn-demand or low-frequency refresh
GET /positions~2 s sustained; avoid >3 parallel5 s background refresh
GET /pnl~2 s sustained5 s background refresh

Prefer WebSocket streams for sub-second order and P&L updates instead of sub-second REST polling.

Locate automation

RuleGuidance
/quoteMax 1 quote/s; no burst
Same symbolWait ≥ 30 s between quotes for the same symbol on the same account
Accept / sell / cancelRespect 10/s (accept, sell) and 2/s (cancel, history, inventory)
ConfirmationPOST/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

Am I limited per API key or per IP?

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.

What should I do after a 429?

Pause, wait 1–2 seconds, retry with backoff. Do not immediately re-fire the same parallel burst.

Do WebSockets count against REST limits?

No. WebSocket connections use a separate model. REST limits apply only to HTTP requests on webapi.tradezero.com.

Does paper differ from live?

Same host and limit model for shared endpoints. Locates are live-only.

Why did my second locate quote return 200 but fail later?

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.

Why is cancel-all stricter than cancel-one?

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

SymptomLikely causeFix
429 on GET /orders or /positionsPolling faster than the limitSlow to ≥1–2 s between reads; serialize parallel fetches
429 only in productionSeveral processes polling or writing in parallelCoordinate into one polling loop or request queue; slow combined traffic to the limits in this doc
429 after parallel POST /orderBurst of concurrent order writesQueue writes; stay within the 10/s limit
Second locate quote returns 200 but does not appear in inventory30 s same-symbol business ruleWait 30 s; check /locates/history for locateStatus
Empty 429 bodyExpectedBack off; do not parse JSON from the body

For authentication, see Authentication. For order and locate semantics, see Equity Trading, Positions, and Short Locates.