429 RATE_LIMIT_EXCEEDED, a Retry-After header and the X-RateLimit-* headers, so you can back off cleanly.
Limits set by your key’s tier
Your key’s tier sets its limits. Read the live values from theX-RateLimit-* headers and from the bodies of 409 and 429 refusals rather than hard-coding them.
A tier sets four independent limits:
- Request rate. Requests per minute. Exceeding it returns
429 RATE_LIMIT_EXCEEDED. - Per-trade cap. The USDC amount of one trade. A larger trade is refused at once with
409 SPENDING_LIMIT_EXCEEDED. - Daily spend cap. Total USDC bought through the key since 00:00 UTC. This is checked after the trade is accepted: a buy that would exceed it moves to
rejectedand fires atrade.rejectedwebhook. Sells do not count against it. - High-value confirmation. On tiers that have one, a trade at or above the threshold returns
202 Acceptedwithconfirmation: { token, expiresAt }. Release it beforeexpiresAtwithPOST /v1/trades/{id}/confirm. This gives your system a chance to stop a runaway bot before it commits a large trade.
429 TRADE_IN_FLIGHT_LIMIT_EXCEEDED and a short Retry-After.
Who shares a quota
- The request rate is counted per user, across every key that user holds, so extra keys do not add throughput. Each key is also counted on its own, with the same limit.
- Spending caps are per key.
- Read routes and write routes draw from separate budgets, each at the tier’s per-minute limit:
- Read routes (every
GET) use a sliding window: smooth, with no bursting. - Write routes (every rate-limited
POST) use a token bucket: its capacity equals the limit and it refills continuously, so short bursts are allowed.
- Read routes (every
Response headers
Every rate-limited response, successful or not, carries the state of the budget closest to running out:X-RateLimit-Limit: the per-minute limit.X-RateLimit-Remaining: requests left before you are refused.X-RateLimit-Reset: Unix time in seconds when the budget is full again.
429.
Hitting the limit
Retry-After is computed for your bucket: the seconds until it admits another request. Always honour it. The TypeScript SDK does this automatically.
If the rate limiter itself is briefly unavailable, requests are refused with 503 RATE_LIMIT_UNAVAILABLE and a short Retry-After. That is not a quota event; retry it.
Temporarily disabled routes
Separately from rate limiting, an individual route can be temporarily unavailable, returning503 ROUTE_DISABLED with a Retry-After header. API trade writes can also be temporarily unavailable, returning 503 API_TRADE_PROCESSING_HALTED with a Retry-After header on trade creation and confirmation while reads keep working. Back off and retry in both cases.
Best practices
- Read the headers and keep
X-RateLimit-Remainingcomfortably above zero under steady load. - Use conditional requests for polling. A
304 Not Modifiedstill counts as a request, but it is cheap. See ETags. - Prefer webhooks to polling trade status.
- Cache quotes briefly. Quote responses carry a short
Cache-Controlmax-age. - Use the SDK. It honours
Retry-Afterwith exponential backoff and jitter, and sends an idempotency key on writes so retries are safe.
Upgrading your tier
RepeatedRATE_LIMIT_EXCEEDED or SPENDING_LIMIT_EXCEEDED during normal operation means you have outgrown your tier:
free→developer: request it from the info button in the Kash web app → Feedback, with a one-line description of your use case.enterprise: ask from the info button in the Kash web app → Feedback, or in Discord.mm: ask from the info button in the Kash web app → Feedback, or in Discord.
Telemetry
GET /v1/account/usage (scope auth:manage) reports the calling key’s rate-limit rejections over the last 24 hours, among other figures:
Next
Errors
The RFC 7807 error format and code catalogue.
ETags
Conditional requests for cheap polling.