Once your calls are attributed to end users (via user: { id, plan } or a client token), Roadie can enforce that user’s limits in addition to your key, environment, and project limits. This tutorial configures per-plan rate limits and quotas for free vs pro, adds a project spend budget as a kill-switch, and blocks an abuser — then shows how each control surfaces to your app. Everything here is set through the control plane (/v1), which you can drive from the dashboard or a management key (rd_mk_…). This tutorial uses a management key so the steps are copy-pasteable; the dashboard has the same knobs.
New to the model? Read Rate limits, quotas & budgets and End users & entitlements first — this tutorial applies them.

Prerequisites

  • A project with a validated provider credential and a secret key for calling the data plane.
  • A management key (rd_mk_…) for the control-plane calls (see Automate with a management key to create one), or use the dashboard.

1. Find your project and environment ids

Limits attach to an environment (rate limits, quotas) or a project (budget). Look them up:
Grab the id of the environment you want to limit (say development) — call it $ENV_ID.

2. Set per-plan rate limits (free vs pro)

A rate-limit policy is scope-addressed. Use the end_user_plan scope with scope_ref set to the plan name, so it applies to every end user on that plan. Give free a tight per-minute cap and pro a generous one:
You can set rps, rpm, rpd, and max_concurrency (at least one is required). burst_multiplier allows short bursts above the sustained rate.

3. Set per-plan quotas (monthly)

Quotas cap a metric over a fixed calendar window. Cap the free plan at a monthly request count as a hard limit (the kill-switch), and give pro a much larger allowance:
metric can be requests, input_tokens, output_tokens, total_tokens, or cost_usd; window is day, month, or billing_period. A soft limit never blocks — it emails the account owner once per window as an early warning; a hard limit rejects with a Retry-After.
Want the same limits for every end user regardless of plan? Use the end_user_default scope (no scope_ref) — it fills in for users without a plan-specific policy.

4. Attribute requests so the policies apply

Per-user limits only bind when a request is attributed to a user. With a server key, pass user (with the plan that selects the preset); with a client token, the id and plan ride in the token.
End users are created lazily on first sight — no registration step.

5. Handle limit errors in your app

Each control raises a distinct, typed failure. The SDK auto-retries the 429s (honoring Retry-After) before they reach you; budget_error (402) surfaces immediately.
Every data-plane response also carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset; the SDK surfaces them as result.rateLimit. One user hitting their limit never affects your other users.

6. Cap total spend with a project budget

Rate limits and quotas bound usage; a budget bounds dollars. Set a monthly USD cap on the project as a hard kill-switch, with soft alerts on the way up:
Check consumption any time:
notify_thresholds are fractions of the cap (0.8 = 80%) and email the account owner as they are crossed. With hard_cap: true, requests are rejected with 402 budget_error once the cap is reached. Roadie does not send outbound webhooks for limit or budget events — alerts are email.

7. Block an individual abuser

Limits are per-plan or per-default; to stop one user immediately, block them. A block writes a Redis denylist entry and takes effect within a second across every gateway instance:
That user’s requests now fail 403 permission_error (end_user_blocked) without affecting anyone else. Reverse it with .../unblock.

Precedence, in one line

For each dimension the gateway picks the most specific policy that applies — end_user_plan → end_user_default → key → environment → project → platform default — then enforces all applicable levels. The first limit that trips rejects the request.

What you built

Each end user now has their own rate limits and monthly quotas that differ by plan, your project has a hard spend budget with email alerts, and you can block an abuser instantly. Your app reacts to RateLimitError, QuotaError, and BudgetError distinctly, so users see the right message — “slow down”, “limit reached”, or your upgrade prompt — and one user can never starve another.

Next steps

Free → Pro with StoreKit

Move a user between the free and pro presets with an entitlement grant.

Observability

See per-user usage and cost, and find who is near a limit.

Automate with a management key

Script these limit changes from CI.

Usage controls guide

The task-oriented reference for every scope and mode.