Throttle scopes
| Scope | Default rate | Applies to |
|---|---|---|
| anon | 60 / minute | Unauthenticated calls (default) |
| user | 600 / minute | Authenticated calls (default) |
| auth | 10 / minute | Login, register, deactivate — credential-touching endpoints |
| otp | 5 / minute | OTP send + verify (per phone) |
| password_reset | 5 / hour | Password reset request + confirm |
| public_read | 120 / minute | Anonymous /api/v1/public/<username>/portfolio/ |
429 response
When you cross a budget, you get a 429 Too Many Requests. The body uses our standard error envelope so the same client code that reads other 4xx responses works here too.
HTTP/1.1 429 Too Many Requests
Retry-After: 28
{
"error": {
"code": "rate_limited",
"message": "Too many requests.",
"details": null,
"request_id": "..."
}
}
Honor Retry-After (seconds). Most HTTP clients have built-in support — for example, in Python:
from time import sleep
import requests
def with_retry(call, max_attempts=3):
for _ in range(max_attempts):
r = call()
if r.status_code != 429:
return r
sleep(int(r.headers.get("Retry-After", 1)))
raise RuntimeError("rate-limited too long")
Best practices
- Cache aggressively on the client side for
public_read. Portfolios change rarely; a 5-minute cache is usually safe. - Issue Personal Access Tokens per integration rather than reusing one. We rate-limit per token; one bad cron job can't starve the others.
- Don't poll — there's no near-real-time data here. If you need to know when a portfolio updates, hit the read API on a webhook trigger from your own system.
- Watch
request_idin error envelopes. If you open a support thread, paste it — we can grep our logs for the exact request.
Limits feel wrong for your workload? Tell us. We bias to fix.