Skip to main content

Rate Limiting

The Mandaitor API enforces rate limits to ensure fair usage and protect service stability. This guide explains what is enforced today, the limits per route, and how to handle rate limit errors in your application.

How Rate Limiting Works​

Limits are enforced at three layers, all in front of the application:

  1. Per-address flood protection (AWS WAF). Each source IP address may make at most 6,000 requests in any five-minute window (about 20 requests per second sustained). Tighter per-address caps apply to the public, credential-free routes listed below. Partners whose traffic leaves through a shared egress address can have that range registered so it is not treated as a single client; contact support@mandaitor.io.
  2. Platform throttles (AWS API Gateway). A token-bucket limit of 500 requests per second with a burst of 1,000 applies to the API as a whole, and stricter per-route limits apply to expensive or public operations. These limits are the ceiling for all callers together.
  3. Per-tenant allowance (AWS API Gateway usage plans). Under that ceiling, every tenant is throttled in its own bucket, so another tenant's burst cannot push you into 429. Your allowance depends on your plan (see Per-Tenant Limits). It is attached to your tenant by the platform when your credentials are validated; you do not send an API Gateway key or any extra header.

When a limit is exceeded, the API returns 429 Too Many Requests. The response carries a Retry-After header when the server can tell you how long to wait.

Monthly volume

Paid plans have no monthly quota: a limit that cut a paying tenant off mid-month would be worse than the problem it solves. Volume commitments for partners are contractual rather than technical.

Free-tier accounts do have a monthly ceiling — see Monthly Allowance below. Your usage and remaining allowance are visible via GET /tenants/{id}/usage (see Billing & Usage).

Per-Tenant Limits​

Each tenant has one token bucket per plan, shared by all of that tenant's API keys and dashboard users. Values are for production.

PlanSustained rateBurstPOST /mandatesLifecycle routes (revoke, suspend, reactivate)POST /tenants/{id}/api-keys
standard50 req/s1005 req/s, burst 103 req/s, burst 61 req/s, burst 2
enterprise200 req/s40010 req/s, burst 205 req/s, burst 101 req/s, burst 3

New tenants start on the standard plan. Exceeding your allowance returns 429 Too Many Requests for your tenant only; the per-route limits below and the platform ceiling still apply on top.

Monthly Allowance​

The limits above bound how fast you can call. Free-tier accounts are additionally bounded in how much: 100,000 billable units per calendar month.

Units are weighted per operation, so the ceiling is not a flat request count:

OperationUnits
POST /verify1
POST /verify?pom=...2
POST /verify?drift=true1.5
POST /decisions2
POST /mandates5
Lifecycle (revoke, suspend, reactivate)2
Reads (GET /mandates, GET /events)0 — never counted

Reads are free and never consume allowance.

A decision counts whether it allows or denies. A DENY costs the same mandate lookup, policy evaluation and audit write as an ALLOW, so it consumes the same units. A request-bound decision counts at the Proof-of-Mandate rate, as the verification with a Proof-of-Mandate it succeeds. Redeeming its authorization (POST /decisions/{id}/redemption) is not counted.

Corrected 10 September 2026

Until this date, denials and verifications that issued a Proof-of-Mandate were not counted at all, and POST /mcp/authorize, removed since, was not counted at all. This was a defect, not a discount. Metered volume on those paths will appear higher from now on — the traffic did not change, the counting did. If it changes what you expect to owe, write to support@mandaitor.io.

What happens at the ceiling. Your API keys are refused for the rest of the calendar month; requests authenticated with an API key return 403. Dashboard sign-in is deliberately unaffected, so you can still open the dashboard, review your usage and subscribe to a paid plan. The allowance resets at the start of the next month with no action needed.

Checking where you stand. GET /tenants/{id}/usage returns an allowance block:

{
"allowance": {
"metered": true,
"period": "2026-09",
"limit": 100000,
"used": 41280,
"remaining": 58720
}
}

A tenant on a paid plan reports "metered": false with limit and remaining as null: paid plans are not capped.

If you need more during an evaluation, write to support@mandaitor.io before you hit the ceiling — the limit is per-account and can be raised without moving you to a paid plan.

Per-Route Limits​

The following routes have their own throttles, shared by all callers and below the platform-wide limit. Values are for production.

RouteLimitRationale
POST /mandates10 req/s, burst 20Each creation involves KMS signing
POST /mandates/{id}/revoke5 req/s, burst 10Lifecycle state change + audit event
POST /mandates/{id}/suspend5 req/s, burst 10Lifecycle state change + audit event
POST /mandates/{id}/reactivate5 req/s, burst 10Lifecycle state change + audit event
POST /tenants/{id}/api-keys1 req/s, burst 3Sensitive, rare operation
POST /onboarding/request2 req/s, burst 5; plus 20 requests per address per 5 minutes and 10 accepted submissions per address per hourPublic endpoint
POST /assistant/docs-chat3 req/s, burst 6; plus 60 requests per address per 5 minutesPublic endpoint, one model call per request
POST /admin/auth/{action}, POST /mcp/token10 req/s, burst 20; plus 100 credential-free requests per address per 5 minutesSign-in and token endpoints (brute-force protection)
POST /mcp25 req/s, burst 50The MCP server (Preview), for every agent together. Each agent also has a limit of its own, below
GET /eudi/request/{id}, POST /eudi/sessions/{id}/response20 req/s, burst 40Public wallet endpoints
GET /widgets/{widgetId}/config20 req/s, burst 40Public read
POST /verify, GET /mandates, GET /eventsPlatform limitRead-only, no per-route restriction
POST /decisions, POST /decisions/{id}/redemptionPlatform limitRequest-bound decisions (Preview); your plan's per-tenant limit applies

Executions (Preview)​

The execution API limits each agent rather than each route:

LimitPast it
30 POST /executions requests a minute, counted whether or not a request is refused. A tool call on the MCP server counts as one; get_execution does not429 RATE_LIMITED, with Retry-After; on the MCP server, a tool error that says when to try again
60 requests a minute to the MCP server, whatever they ask, counted whether or not a request is refused. A tool call counts here as well as against the 30 above429, with Retry-After and a JSON-RPC error (-32029). Nothing is read for the request
20 approval requests a UTC dayA request that needs a person's approval is denied with APPROVAL_LIMIT_REACHED

Execution requests, and requests to the MCP server, do not count against the monthly allowance today.

Handling Rate Limit Errors​

When you exceed a limit, the API returns:

{
"error": "TOO_MANY_REQUESTS",
"message": "Rate limit exceeded"
}

Requests blocked at the WAF layer return a 403 with an empty body instead; treat a 403 on a route you are otherwise authorised for, arriving after a burst of traffic, the same way as a 429.

Implement exponential backoff with jitter to gracefully handle rate limiting:

async function withBackoff<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error: any) {
const status = error?.status ?? error?.response?.status;
if (status !== 429 || attempt === maxRetries) throw error;

const delay = baseDelay * Math.pow(2, attempt);
const jitter = delay * 0.5 * Math.random();
await new Promise((r) => setTimeout(r, delay + jitter));
}
}
throw new Error("Unreachable");
}

Best practices for staying within limits​

  1. Batch where possible: If you need to create multiple mandates, space out the requests rather than sending them all at once.

  2. Cache verification results: If you verify the same action repeatedly within a short time window, cache the result on your side instead of calling the API each time.

  3. Use read endpoints efficiently: Use the limit and cursor pagination parameters to fetch only the data you need.

  4. Monitor your usage: Track your request volume with GET /tenants/{id}/usage so that agreed volumes are visible to both sides.

Need Higher Limits?​

If your integration needs more than the standard per-tenant allowance, more than the per-address allowance (for example, many agents behind one egress address), or a per-route limit is too low for your use case, contact us at support@mandaitor.io. Partners can be moved to the enterprise plan without any change on their side, and registered partner egress ranges receive a separate, higher per-address cap.