Rate limits

Quotas per scope, not per endpoint.

Endpoints share throttle scopes so a noisy integrator can't starve the rest of the surface. The numbers below are defaults; we may bump or tighten any of them in response to abuse — don't hardcode.

All docs / Rate limits

Throttle scopes

Scope Default rate Applies to
anon60 / minuteUnauthenticated calls (default)
user600 / minuteAuthenticated calls (default)
auth10 / minuteLogin, register, deactivate — credential-touching endpoints
otp5 / minuteOTP send + verify (per phone)
password_reset5 / hourPassword reset request + confirm
public_read120 / minuteAnonymous /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_id in 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.