Rate limits
Per organization, per minute, and published rather than discovered as a random failure.
Policy
| Dimension | Value |
|---|---|
| Scope | Per organization (api:{org_id}) |
| Window | Fixed 1-minute window |
| Applies to | All partner API requests after a valid token resolves and entitlement passes |
Per-plan ceiling (resolved server-side by org_api_rate_limit()):
| Plan | Requests / minute |
|---|---|
Freelancer Solo, legacy solo | 60 |
Freelancer Studio, legacy studio*, Change Practitioner | 120 |
| Freelancer Agency, Change Consultant / Firm / Team | 300 |
Every paid tier has API access — migration 20261112000600 moved api_access off the
top tier. The differentiator is volume, not access, which is why this table exists
and why the numbers are published rather than discovered as random failure.
All endpoints share one org bucket: /api-list-clients, /api-create-deliverable,
/api-ingest-request, and every /api-v1/* route.
Response headers
Successful /api-v1/* responses carry the IETF RateLimit fields, in both the
current and the superseded shape:
RateLimit-Policy: "api";q=300;w=60
RateLimit: "api";r=287;t=41
RateLimit-Limit: 300
RateLimit-Remaining: 287
RateLimit-Reset: 41
X-Stria-Api-Version: v1
RateLimit-Policyand the single-lineRateLimitfield are the current shape in
draft-ietf-httpapi-ratelimit-headers: q is the quota, w the window in
seconds, r the quota remaining, t the seconds until the window resets.
RateLimit-Limit,-Remainingand-Resetare the superseded fields. They are
still sent because they are published here and clients depend on them; they will
be retired on a documented Sunset date, not silently.
r and t are measured, read off the counter the request just incremented, not
estimated. They were previously omitted for a good reason — enforce_rate_limit
raises on breach and reports nothing, so any figure would have been invented — and
that reason no longer applies since consume_rate_limit returns the counter it
writes.
Behaviour
- Token is validated (format + hash + not revoked).
api_accessentitlement is checked.consume_api_rate_limit(org_id)resolves the plan ceiling and spends one
request against it in a single call.
- Over the ceiling within the window → 429 with
Retry-Afterset to the
measured seconds remaining in the window.
Step 3 is one call on purpose. Until migration 20261115000100 the ceiling was
resolved in one place and enforced in another: every request was throttled at the
flat 60 while the response advertised the org's plan ceiling, so a Studio or Agency
subscriber received RateLimit-Limit: 120 or 300 and 429s at 60. Resolving and
spending in the same statement makes the number enforced and the number published
the same number by construction.
Failed auth (401) does not consume quota — the limiter runs only after a successful
token lookup and entitlement check, so an invalid token cannot exhaust another org's
budget.
429 response
{
"error": "Rate limit exceeded. Please wait a few minutes and try again.",
"code": "rate_limit_exceeded",
"retry_after_seconds": 41,
"limit": 300
}
retry_after_seconds matches the Retry-After header and both carry the real time
left in the window. Back off by that value plus jitter.
Client guidance
- Treat
429as temporary. Back off at leastRetry-Afterseconds, plus jitter. - For a nightly reconciliation, prefer
updated_sincewith a largelimitover many
small requests — one page of 200 costs one request.
- For Figma batch export, serialise uploads and stay well under the ceiling.
Operational notes
- Counters live in
rate_limit_counters, purged by thestria-purge-rate-limitscron
job.
- Edge Functions call
enforce_rate_limitasservice_role. - Ceilings may be raised without notice. A reduction is a breaking change and will
appear in changelog.md with notice, per
compatibility.md.
More reference
- Authentication — How tokens are minted, stored, presented and revoked.
- Endpoints — Every request and response shape, with worked examples.
- Outbound webhooks — Signed HTTPS POSTs when work is signed off, a change order moves, or an invoice is paid — plus how to verify one and how to survive a duplicate.
- Errors — What each status means, what the body carries, and which ones to retry.
- Compatibility and versioning — What may change without notice, what may not, and how you are told.
- API changelog — Every change, dated, including the ones that were corrections.
OpenAPI specification · API access is on every paid plan · Start your 14-day trial