User-managed access and rate limiting: a technical guide
User-Managed Access lets a resource owner decide who else may reach their data, through permission tickets and requesting party tokens. Every identity that grant creates is also a quota key. The protocol, the HTTP signals for throttling, and how both apply to LLM APIs and agents acting for users.
TL;DR
- User-Managed Access (UMA 2.0) is a Kantara Initiative extension of OAuth 2.0 that adds a requesting party: someone other than the resource owner who is granted access under policies the owner sets in advance.
- The client earns access by trading a single-use permission ticket for a requesting party token (RPT); resource servers check RPTs through an extended version of RFC 7662 token introspection.
- Delegated access creates several identities per request (client, requesting party, resource owner, resource), and each one is a natural rate-limit key. Limiting only by API key or IP throws that information away.
- Throttled callers get HTTP 429 (RFC 6585) and should be told when to retry with Retry-After (RFC 9110); the IETF RateLimit header draft, now at version 11, adds machine-readable quotas with partition keys.
- LLM providers limit at the organization or project level, not per user, so per-user and per-agent quotas have to be enforced in front of them, typically in an AI gateway such as Bifrost.
User-Managed Access (UMA) is an OAuth-based protocol that lets the owner of a protected resource decide which other people or organizations may reach it, under what conditions, without being present when they ask. Rate limiting decides how much any caller may consume. The two meet whenever an API serves delegated access: the same token that tells a server who is asking, on whose behalf and through which application also tells it which quota to charge. That is now the normal shape of AI traffic, where agents call models and tools for a named user, so identity-aware limits matter more than they did when one API key meant one application.
What User-Managed Access is
User-Managed Access 2.0 is a pair of specifications from the Kantara Initiative’s UMA Work Group, published in January 2018. The UMA grant extends OAuth 2.0 so a resource owner can authorize a separate requesting party; an optional federated authorization specification standardizes how resource servers and an authorization server cooperate.
The UMA 2.0 grant is defined as an extension OAuth grant that enables “party-to-party authorization, rather than authorization of application access alone.” Its second change is timing. The owner configures policy conditions at the authorization server “at will,” and the requesting party’s access is evaluated asynchronously, instead of the owner approving a token right after logging in.
The specification’s own example shows why that matters. A bank customer, Alice, uses a sharing service hosted by her bank to manage access to her accounts by her spouse, her accountant and a financial aggregation company, each using a different client application, with separate scopes for viewing data and accessing payment functions. Plain OAuth can let an app act as Alice. It has no standard way to let Bob act as Bob on Alice’s account.
The companion Federated Authorization specification extends that to several resource servers in different domains reporting to one authorization server that acts for the owner. Together the two documents “constitute UMA 2.0.” For access control models that sit alongside UMA, such as attribute-based policies evaluated at the authorization server, the Frontier Wire guide to attribute-based access control covers the policy side in more depth.
The UMA 2.0 roles and how they map to OAuth
UMA keeps OAuth’s four roles and adds one. The resource owner, client, resource server and authorization server carry over from RFC 6749; the new requesting party is the person or organization actually using the client, who may or may not be the owner.
RFC 6749 defines the client as “an application making protected resource requests on behalf of the resource owner and with its authorization.” UMA changes that sentence. In the UMA roles, the client acts “with the resource owner’s authorization and on the requesting party’s behalf,” and the resource owner is “the ‘user’ in User-Managed Access,” which may be a natural person or a legal person such as a corporation.
| Role | In OAuth 2.0 (RFC 6749) | In UMA 2.0 | Typical quota question |
|---|---|---|---|
| Resource owner | Grants access, usually to an app acting for them | Sets policy conditions for other parties, ahead of time | How much load may others put on my data? |
| Requesting party | Not defined; same as the owner | A natural or legal person using a client to seek access | How much may this person consume? |
| Client | Acts on the owner’s behalf | Acts on the requesting party’s behalf, with the owner’s authorization | How much may this application consume? |
| Resource server | Hosts resources, accepts access tokens | Hosts resources, obtains permission tickets, enforces RPTs | How much can this API serve in total? |
| Authorization server | Issues access tokens | Assesses claims against policy, issues RPTs | How often may a client ask for tokens? |
The last column is the point of this guide. Each role is an identity the enforcement point can see, and each is a reasonable thing to count.
How the UMA grant works: permission tickets and RPTs
In UMA, a client that asks for a resource without a token receives a 401 carrying a permission ticket and the authorization server’s address. It trades that ticket at the token endpoint for a requesting party token, or RPT, if the requesting party’s claims satisfy the owner’s policy, then retries with the RPT.

Figure 1: The resource owner sets policy at the authorization server at any time; the requesting party never needs the owner online.
Figure 1 follows the steps in the spec:
- Tokenless request. The client calls the resource server with no access token. The resource server must obtain a permission ticket from the authorization server and responds with
401 Unauthorizedand aWWW-Authenticate: UMAheader containingas_uriandticket. - Ticket for token. The client posts to the token endpoint with
grant_type=urn:ietf:params:oauth:grant-type:uma-ticketand the ticket, optionally pushing a claim token such as an OpenID Connect ID token, or a persisted claims token (PCT) from an earlier run. - Assessment. The authorization server computes the requested scopes per resource, evaluates the owner’s policy conditions against the claims, and issues an RPT holding the scopes that passed. The spec’s worked example shows a client asking to edit an album and receiving, at most, view access to one photo.
- Retry with RPT. The client presents the RPT as a bearer token, and the resource server “MUST NOT give access in the case of an invalid RPT or an RPT associated with insufficient authorization.”
Failures are explicit. The error codes include need_info (more claims needed, with a fresh ticket and either required_claims or a redirect_user hint), request_denied, and request_submitted, which means the owner must intervene and lets the server send an interval that sets the minimum polling gap in seconds.
That interval is UMA’s only built-in pacing rule, and it is a hint for one authorization process. The RPT itself is described as “unique to a requesting party, client, authorization server, resource server, and resource owner,” which is exactly the composite identity a rate limiter wants. The spec also requires that permission tickets be single-use and invalidated when presented or expired, so a ticket cannot be replayed to inflate a quota.
Federated authorization: PAT, resource registration and introspection
The UMA federated authorization specification defines a protection API that the authorization server exposes to resource servers. It is OAuth-protected with a protection API access token (PAT) and covers resource registration, the permission endpoint that issues tickets, and an extended token introspection endpoint that returns per-resource permissions.
A PAT is an ordinary OAuth access token with the scope uma_protection, issued by the resource owner to the resource server. Holding it is how the resource server proves it may register the owner’s resources and ask for tickets. The specification describes the split as similar to the enterprise pattern of policy decision points and policy enforcement points, and adds that “the resource server MAY apply additional authorization controls beyond those imposed by the authorization server.”
That sentence is where rate limiting legally lives in UMA. A valid RPT says the requesting party may view a resource. It does not say how often. The resource server, or a gateway acting as its enforcement point, keeps the right to refuse a request that is authorized but excessive.
Three parts of the protection API matter for quota design:
- Resource registration. The resource server registers each resource with
resource_scopes, and the authorization server returns aresource_id. That identifier is stable and owner-specific, which makes it a clean per-resource quota key. - Permission endpoint. The resource server posts a resource ID and scopes and gets back one ticket. Permissions can only be requested for one resource owner at a time, so every ticket maps to exactly one owner.
- Token introspection. The resource server can introspect an RPT and receive a
permissionsarray ofresource_id,resource_scopesand optional per-permissionexp, instead of a flatscopestring.
Introspection itself comes from RFC 7662. Its response includes active, client_id, username, sub (usually a machine-readable identifier of the resource owner), aud and exp. RFC 7662 allows the resource server to cache that response “at the cost of liveness,” and its security considerations warn that an unprotected and “un-throttled” introspection endpoint lets an attacker fish for valid tokens. The authorization server is therefore a rate-limited resource too.
The best-known open implementation is Keycloak, whose Authorization Services are “built on top of well-known standards such as the OAuth2 and User-Managed Access specifications” and include a UMA-compliant permission endpoint and an account console page where users manage who can reach their resources.
UMA and OAuth 2.0 compared
UMA is not a replacement for OAuth 2.0; it is an extension grant that runs on the same token endpoint and bearer tokens. The differences are who can be authorized, when the owner decides, and what the access token represents. Those differences change which identities a rate limiter can read from a request.
| Property | OAuth 2.0 authorization code grant | UMA 2.0 grant |
|---|---|---|
| Who gains access | A client acting for the resource owner | A client acting for a requesting party, who may be someone else |
| When the owner decides | Synchronously, at consent | Ahead of time, through policy conditions at the authorization server |
| Grant type | authorization_code | urn:ietf:params:oauth:grant-type:uma-ticket |
| What starts the flow | Client redirects the user to authorize | Resource server returns a 401 with a permission ticket |
| Token contents | Scopes for the client | Permissions per resource, each with scopes and optional expiry |
| Partial grants | Scopes may be narrowed | RPT may hold fewer permissions than requested, or an error is returned |
| Identities visible to the API | Client, owner | Client, requesting party, owner, resource |
Rate limiting on delegated access: choosing the quota key
Identity-aware rate limiting counts usage against the identities in the access token rather than against an IP address or a single API key. In delegated access, a request carries a client, a requesting party, a resource owner and a resource, and each protects something different. Most production APIs enforce more than one of these counters at once.
The status code for throttling does not choose the key for you. RFC 6585 says plainly that it “does not define how the origin server identifies the user, nor how it counts requests,” and that a server “might identify the user by its authentication credentials, or a stateful cookie.” Algorithms such as token bucket, fixed window and sliding window answer how to count; the Frontier Wire explainer on rate limiting compares them. This section is about whom to count.

Figure 2: Every identity in a delegated request is a possible quota key; the token is where the enforcement point reads them.
| Quota key | Where it comes from | What it protects | Failure it prevents |
|---|---|---|---|
| Per client | client_id in the token or introspection response | The API from one faulty or abusive application | A buggy integration retrying in a loop for every user |
| Per requesting party | sub of the caller, or the requesting party bound to the RPT | Fair share between people using shared clients | One user of a popular app exhausting everyone’s allowance |
| Per resource owner | sub or username of the owner; the PAT’s owner | The owner’s data and downstream bill | Many parties hammering one person’s resources at once |
| Per resource | resource_id from UMA, or route | Hot resources and expensive operations | A single shared document or model becoming a bottleneck |
| Per token or grant | jti, or the RPT itself | Containment of a leaked credential | A stolen token used at full speed until it expires |
| Global | None | Total capacity | Overload regardless of who sends it |
Three design rules follow from the specs:
- Read identity from the verified token, not from client-supplied headers. The RateLimit draft says servers “SHOULD only use information that is present in the request” to build partition keys, and RFC 7662 makes the authorization server responsible for checking expiry, revocation, signature and audience before reporting a token active.
- Charge every applicable counter. A request should pass the client, party and owner checks independently. If only the client is counted, a requesting party can multiply their share by switching apps; if only the party is counted, one client can still overload the owner’s resources.
- Revocation beats throttling. UMA’s privacy section suggests invalidating an RPT and refresh token as soon as the owner narrows a policy. A rate limit slows an unwanted caller; withdrawing the grant stops it.
For LLM applications the stakes are named directly: Unbounded Consumption sits sixth in the 2026 edition of the OWASP Top 10 for LLM Applications, and a limit keyed on the wrong identity does little to contain it.
Signalling limits: 429, Retry-After and RateLimit headers
A throttled request should return HTTP 429 Too Many Requests, defined in RFC 6585, ideally with a Retry-After header from RFC 9110 giving a delay in seconds or an HTTP date. The IETF RateLimit header draft adds two fields, RateLimit-Policy and RateLimit, that advertise quotas and remaining capacity before a client hits the limit.
RFC 6585 defines 429 as the user having “sent too many requests in a given amount of time,” says the response “MAY include a Retry-After header,” and requires that 429 responses “MUST NOT be stored by a cache.” Its example is identity-aware: a site allowing 50 requests per hour per logged-in user. Retry-After in RFC 9110 takes either an HTTP-date or a non-negative number of seconds.
The RateLimit header fields draft, version 11, published 23 May 2026, defines:
RateLimit-Policy, a stable list of named policies with a quotaq, an optional unitqu(requests,content-bytesorconcurrent-requests), a windowwin seconds and a partition keypk.RateLimit, the current service limit per policy: remaining quotar, effective windowtand partition keypk.
The partition key is the field that connects the draft to delegated access. The draft’s own example policy is named peruser, and its section on generating partition keys lists strategies “including per user, per application, per HTTP method, per resource,” while warning that servers “should avoid returning partition keys that contain sensitive information.” A server applying both a per-requesting-party and a per-owner limit could advertise both:
HTTP/1.1 200 OK
RateLimit-Policy: "party";q=600;w=60;pk=:cGFydHktNDI=:, "owner";q=5000;w=3600;pk=:b3duZXItNw==:
RateLimit: "party";r=12;t=41;pk=:cGFydHktNDI=:, "owner";r=4100;t=1800;pk=:b3duZXItNw==:
Two boundaries in the draft are worth keeping in mind. Its goals section states that the fields “are not meant to support authorization or other kinds of access controls,” so quota headers sit beside an UMA or OAuth decision, never in place of one. And when a response carries both RateLimit and Retry-After, the client rule is that “the Retry-After field MUST take precedence.” Earlier drafts used different field names; Kong’s rate limiting plugin, for example, documents RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers “based on the Internet-Draft” and notes they may change as the specification moves.
Status codes need to stay distinct, because clients act on them differently:
| Status | Meaning in a delegated-access API | Client action |
|---|---|---|
| 401 | No valid token; in UMA, carries as_uri and a permission ticket | Obtain or refresh a token, or start the UMA grant |
| 403 | Token valid but lacks the permission, or the grant was denied | Request more scope, push claims, or stop |
| 402 | Some gateways use it for an exhausted spend budget | Raise or wait for the budget; retrying will not help |
| 429 | A rate or quota counter is exhausted | Wait for Retry-After or the effective window, then retry with jitter |
| 503 | Server-side capacity problem, not the caller’s quota | Back off; Retry-After may say for how long |
Delegated access and quotas for LLM APIs and agents
LLM providers enforce limits per organization or project, measured in requests and tokens per minute, and do not distinguish the end users or agents behind one key. Per-user quotas for AI applications therefore have to be enforced by the application or a gateway in front of the provider, keyed on the identities the delegated-access token carries.
OpenAI’s rate limit guide states that limits are defined at the organization level and at the project level, “not user level,” across measures that include requests per minute and tokens per minute. Anthropic’s documentation says the API enforces limits at the organization level and lets customers set lower limits per workspace; a workspace limit is set per limiter type, and “Organization-wide limits always apply, even if Workspace limits add up to more” (workspace limits).
Token-based limits behave differently from request counts, and the providers count differently:
- Input and output are separate budgets at Anthropic. Limits are requests, input tokens and output tokens per minute. For most current Claude models, cached input tokens do not count toward the input limit, and
max_tokensdoes not factor into the output limit. The Frontier Wire comparison of prompt caching and semantic caching covers why that changes an agent’s effective throughput. - OpenAI reserves against
max_tokens. Its guide says the limit is calculated as the maximum of max_tokens and an estimate from the request’s character count, so oversizedmax_tokensvalues consume quota that is never used. - Headers are vendor-specific. Anthropic returns
retry-afterandanthropic-ratelimit-*response headers with reset times in RFC 3339 format, and notes that a spend-cap 429 never carriesretry-after. OpenAI returnsx-ratelimit-*headers, including project-scoped token headers, and advises clients to follow Retry-After when present and fall back to exponential backoff with jitter.
The consequence for a product that serves many users from one provider project is that a per-user limit must be stricter than the provider’s, measured in the same units (tokens as well as requests), and enforced before the call is sent. Otherwise the first user to trigger the provider’s 429 throttles everyone else on the project. For coding agents, where a single developer can send long contexts in a loop, the Frontier Wire’s breakdown of Claude Code pricing shows how quickly one seat’s usage adds up.
Agents make the delegated-access part explicit. The Model Context Protocol authorization specification makes a protected MCP server an OAuth 2.1 resource server and the MCP client an OAuth client acting “on behalf of a resource owner.” MCP servers must validate that tokens were issued for them as the intended audience, using RFC 8707 resource indicators, and the specification forbids passing a client’s token through to downstream services. An agent calling a tool for a user is a client acting for a requesting party, the same arrangement UMA formalized, and the tool server can key quotas on the user, the agent’s client ID and the tool. The Frontier Wire’s guide to the Model Context Protocol explains the transport and tool model this sits on.
Neither MCP nor the LLM APIs cited above uses the UMA grant itself; they rely on OAuth 2.1 or API keys. What UMA contributes is the vocabulary and the separation it insists on: the owner’s policy decides whether a party may act, and the enforcement point separately decides how much.
How an AI gateway enforces identity-aware limits: the Bifrost example
An AI gateway sits between applications and model providers and can apply per-user, per-team and per-customer budgets and rate limits before a request reaches the provider. Bifrost, an open-source gateway, implements this with virtual keys arranged in a hierarchy, and is used here because its documentation specifies every check and error code.
Bifrost is an open-source gateway written in Go (source on GitHub) that serves 25+ providers and 10,000+ models through one OpenAI-compatible API. Its published benchmark reports 11 µs of overhead at 5,000 RPS on an AWS t3.xlarge, which matters for a component that evaluates several counters on every request.
The unit of governance is the virtual key, which users and applications present in an x-bf-vk header or in the provider-style Authorization, x-api-key or x-goog-api-key headers. A key carries allowed providers and models, a budget, request and token rate limits, an optional expiry, and an owner. The budget and limits page describes two limit types that run in parallel: request limits and token limits counting prompt plus completion tokens, with reset windows from one minute to one year.

Figure 3: A per-user key sits inside team and customer quotas, so one person cannot exhaust a shared allocation.
As Figure 3 shows, limits are checked in hierarchical order: provider config, virtual key, team, then customer, and a request must pass both the request and token checks at every applicable level. Budgets follow the same pattern, and the budget checking flow deducts the same cost from each level. A provider config that exceeds its limit is excluded from routing while other providers on the same key stay available.
Mapped onto the quota keys from earlier, a virtual key per user or per agent is the per-requesting-party counter, the team is a shared group quota, the customer is the per-tenant counter, and the provider config is a per-resource limit on one upstream. For multi-tenant products, a customer-scope header attributes a request to one customer when a team serves several.
Refusals use distinct error responses, close to the status table above: 401 when no key was presented, 403 for an inactive or expired key or a disallowed model, provider or MCP tool, 429 with a type of rate_limited, token_limited or request_limited, and 402 with budget_exceeded when spend runs out. The message names the level that stopped the request.
On the agent side, Bifrost’s MCP authentication supports per-user OAuth and per-user headers, storing each user’s credential against their identity (virtual key, signed-in user or session ID) so a tool call reaches the upstream MCP server as that user rather than under one shared credential.
Further reading on the same design covers per-user rate limiting and budget controls in an enterprise AI gateway, LLM API rate limiting with virtual keys and budgets and budget and rate limit architecture for multi-tenant LLM platforms.
On the tool side, a companion piece explains MCP authentication with OAuth, API keys and token management.
Enterprise tier. Identity-provider tokens in place of virtual keys, access profiles that give “each user” an independent copy of a policy with isolated budget and rate-limit counters, customer scoping and token exchange for MCP are part of Bifrost Enterprise.
Other gateways cover parts of the same ground. LiteLLM’s proxy can apply a budget across all calls an internal user makes and supports per-key rpm_limit and tpm_limit. Kong’s rate limiting plugin counts by the authenticated Consumer when an auth plugin is configured, falls back to client IP otherwise, and from version 3.16 can limit by principal.
The broader trade-offs of putting a gateway in the request path are covered in the Frontier Wire’s piece on running an AI gateway in front of every model call and its guide to LLM cost control with an AI gateway. For more on how virtual keys are used across large engineering teams, see how platform teams govern LLM access with virtual keys.
A design checklist for identity-aware limits
Designing limits for delegated access comes down to deciding which identities to trust, which to count, how to signal exhaustion and how to keep the limiter itself from becoming a weak point. The list below follows the specifications cited above and applies whether the grant is UMA, plain OAuth or a gateway-issued key.
- Separate the decision from the quota. Let the authorization server decide whether a party may act (UMA policy, OAuth scopes, MCP audience checks). Let the enforcement point decide how much. The RateLimit draft and UMA’s federated authorization spec both draw this line.
- Derive keys from verified claims. Use
client_id,subandresource_idfrom a validated JWT or an introspection response, never from a header the client controls. Cache introspection briefly, accepting RFC 7662’s trade-off between performance and liveness. - Count every party that can be hurt. At minimum, a per-client counter and a per-requesting-party counter; add a per-owner counter when many parties share one owner’s resources, and a per-resource counter for expensive operations.
- Match the upstream’s units. For LLM traffic, limit tokens as well as requests, and account for how the provider counts cached tokens and
max_tokens. - Protect the authorization endpoints. Throttle the token and introspection endpoints, enforce single-use permission tickets, and honour UMA’s
intervalwhen polling afterrequest_submitted. - Signal clearly. Return 429 with Retry-After for rate limits, keep 401, 403 and budget errors distinct, and consider RateLimit-Policy and RateLimit headers with non-sensitive partition keys.
- Prefer revocation for withdrawn consent. When an owner narrows a policy, revoke the RPT rather than relying on a quota to slow the caller down.
The pattern that runs through all of it is that delegated access already tells the server who is involved. UMA gives each party a name and a token that binds them together; the HTTP specifications give a way to say “not now” and “try again in 41 seconds.” An API that throttles only by key or address discards the first and makes the second less useful. For AI systems, where an agent’s request can carry a user, an application, a tenant and a model at once, the enforcement point that reads all of those identities is the one that can keep one caller’s loop from becoming everyone’s outage.
Sources
- Kantara Initiative: User-Managed Access (UMA) 2.0 Grant for OAuth 2.0 Authorization
- Kantara Initiative: Federated Authorization for User-Managed Access (UMA) 2.0
- RFC 6749: The OAuth 2.0 Authorization Framework
- RFC 7662: OAuth 2.0 Token Introspection
- RFC 6585: Additional HTTP Status Codes (429 Too Many Requests)
- RFC 9110: HTTP Semantics, Retry-After
- IETF draft-ietf-httpapi-ratelimit-headers-11: RateLimit header fields for HTTP
- Model Context Protocol specification (2025-11-25): Authorization
- Keycloak: Authorization Services Guide
- Anthropic: Rate limits
- OpenAI: Rate limits guide
- Kong: Rate Limiting plugin
- LiteLLM: Budgets and rate limits for users and teams
- Bifrost docs: virtual keys
- Bifrost docs: budget and limits
- Bifrost docs: access profiles
- Bifrost docs: MCP authentication
- Bifrost source repository (GitHub)
Questions readers ask
What is User-Managed Access (UMA)?
User-Managed Access is a Kantara Initiative standard that extends OAuth 2.0 so a resource owner can authorize other people or organizations, called requesting parties, to reach their protected resources. Version 2.0 has two parts, the UMA grant and an optional federated authorization specification, and the owner sets policy at an authorization server ahead of time rather than approving each request.
What is a permission ticket in UMA?
A permission ticket is a single-use correlation handle that the authorization server creates to represent the permissions a client asked for. The resource server returns it in a 401 response, and the client presents it at the token endpoint with the grant type urn:ietf:params:oauth:grant-type:uma-ticket to request a requesting party token, or RPT.
What is the difference between UMA and OAuth 2.0?
OAuth 2.0 lets a resource owner authorize a client application to act for the owner, usually at the moment of login. UMA 2.0 is an extension grant on top of OAuth that adds a separate requesting party, so the owner can grant access to other people or organizations, and it decouples the owner's decision from the time of access through policies held at the authorization server.
Should API rate limits be per user or per client?
Usually both. A per-client limit protects the API from one misbehaving application, a per-user or per-requesting-party limit stops one person from consuming a shared allocation, and a per-resource-owner limit protects the owner whose data is being read. In delegated access the access token carries all of these identities, so each can key its own counter.
What is the difference between 401, 403 and 429 responses?
A 401 means the request lacked valid credentials, and in UMA it carries a permission ticket and the authorization server location. A 403 means the credentials were understood but do not grant the requested access. A 429, defined in RFC 6585, means the caller sent too many requests in a given time and may carry a Retry-After header saying how long to wait.
