Limits
Every ceiling, what happens at it, and how to stay under it.
Limits fall in four groups: how fast you may call, how big a request may be, what one run may do, and what your plan allows over a month. Each one fails in a specific, documented way — none is silent.
Rate limits
| Who | Standard limit | Counted per |
|---|---|---|
| A request with a valid API key | 300 requests a minute | Your API client — every key of your organisation shares one allowance, rotated keys included. |
| A request with no key, or a wrong, revoked or expired one | 30 requests a minute | The caller's IP address. |
- Both limits use a one-minute sliding window. A request over the limit is refused at once, not queued.
- The limit counts requests, not runs or tokens. A streaming run is one request however long it streams.
- Because an invalid key falls into the small per-IP allowance, a worker that keeps sending a
revoked key gets
401s, then429s once that allowance is used up. If you get 429s straight after a rotation, check which key the caller is using.
Handling a 429
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/problem+json
{
"title": "Too many requests.",
"status": 429,
"detail": "Too many requests. Retry after 12 seconds.",
"instance": "/v1/agents/amy/runs/await"
}The answer carries a Retry-After header in whole seconds. There are no RateLimit-* headers, so you cannot read your remaining allowance in advance; count
your own requests if you need to stay under it. A 429 is the one 4xx you can
resend unchanged once the wait is over — the request was refused before anything ran.
async function callAmy(url: string, init: RequestInit, attempts = 4) {
for (let attempt = 1; ; attempt++) {
const response = await fetch(url, init);
if (response.status !== 429 || attempt === attempts) return response;
// Retry-After is in seconds. Wait that long, plus a little jitter.
const seconds = Number(response.headers.get('Retry-After') ?? 5);
await new Promise((r) => setTimeout(r, seconds * 1000 + Math.random() * 500));
}
}Request limits
| What | Limit | When exceeded |
|---|---|---|
| JSON request body | 1 MB | 413 "Payload too large." |
message | 8,000 characters, not empty | 400 |
endUserRef | 200 characters; printable ASCII with no spaces | 400 |
tools | 32 names per run, each up to 100 characters | 400 |
mcpServers (inline) | 16 per run; 1–64 tools each | 400 |
mcpAuth | 16 per run; each token up to 8,192 characters | 400 |
attachmentFileIds | 8 per run | 400 |
One uploaded file (POST /v1/files) | 25 MB | 413 (a file only just over the limit can get a 400) |
| An attached image or audio file | 20 MB; images PNG, JPEG, WebP or GIF; audio MP3, MP4, WAV, WebM, Ogg or FLAC | 400 naming what was wrong, when you attach it |
| An attached PDF | 20 pages and 20 MB; counts as one attachment | 400 naming what was wrong, when you attach it |
History replayed with threadConversationHistory | the newest 20 runs and 48,000 characters | Older runs are dropped whole; a turn is never cut. |
Validation errors are 400 ProblemDetails naming the field in errors (the
key names the field and may be written in PascalCase, such as Message), before any
run is created and before anything is billed. Files outside the bounds above still upload; they
are refused later when you attach them to a run. See Files.
Per-run limits
These bound what a single run may do, counted across the whole run including any approval pauses. They are defaults; an organisation Admin can tune some of them per agent.
| What | Default |
|---|---|
| Max iterations (model-and-tool rounds in one run) | 20 (configurable per agent, 1–100). Reaching it ends the run gracefully with what it has, not with an error. |
| Max output tokens (the longest single reply) | 32,768 (configurable per agent, 1–128,000). A reply that reaches it ends the run with
status truncated and the partial answer. |
| Max web searches per run | 8 per agent, and 20 per provider key across all the agents in one run |
| Max image analyses per run (reading an image or a document) | 4 per agent, and 10 per provider key across all the agents in one run |
| Speech (transcribe and speak) | 4 per agent, and 10 per provider key across all the agents in one run |
| Run summaries | 3 per run |
| Images fetched from the web | 4 per run |
| Spreadsheet calls | 3 per run; up to 5 sheets of 10,000 rows each |
| Specialists working in parallel · delegation depth | up to 4 (parallel calls are off by default) · 2 |
There is no cap on how many of your own MCP tool calls a run makes, other than the round
limit above. When a metered tool hits its cap, further calls are refused and the run goes on; the
run's detail (GET /v1/runs/{runId}) lists the tool families that were
capped in cappedToolFamilies: web_search, vision, speech, run_summary and web_image.
Monthly token allowance
Your plan includes a number of tokens a calendar month (UTC) (amy.tokens.month). The
free plan includes 100,000; the paid plans include more — the pricing page and Console → Billing show the live numbers
for your plan.
- The allowance is checked when a run starts. A submitted run is checked again when it comes up, and a run waiting on background tasks when it wakes. A run already under way is not cut off if it crosses the line; the next start is refused.
- It counts tokens spent through Amy's provider keys. Calls made with your organisation's own provider key — the normal set-up in Set up your organisation — are not counted against it. Your provider bills those directly.
- Counting runs from the first of the month, UTC. It includes runs still in flight and runs that failed or were stopped after calling the model.
At the limit, a run is refused before it starts with a 403:
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"type": "/errors/entitlement-required",
"title": "Plan entitlement required.",
"status": 403,
"detail": "amy.tokens.month",
"reason": "limit_reached",
"limit": 100000,
"remaining": 0
}Branch on reason: limit_reached means the allowance is used up, not_in_plan means your plan does not include it at all. A submitted run, or a run
waking from a wait on background tasks, that finds the allowance used up ends as failed with the code quota_exceeded. A second monthly limit, a spending
budget in money (amy.cost.month), is the one that can stop a run mid-way (status budget_exceeded, code cost_budget_exhausted); own-key spend is not
counted against it either. See Usage for reading your usage.
Plan limits
| Limit | Free | Starter | Pro |
|---|---|---|---|
| Tokens a month (Amy-funded) | 100,000 | 5,000,000 | 100,000,000 |
| Live API keys | 2 | 10 | 50 |
| File storage | 100 MB | 1 GB | 10 GB |
These are the values the plans carry today; the pricing page is the source of truth and can change
them. Creating a key over the key limit, or uploading past the storage limit, is refused with the
same 403 entitlement-required shape as above. There are no plan limits on the number of agents,
MCP servers, webhook endpoints or members.
Idempotency keys
Send an Idempotency-Key header — a UUID you generate is best, but any string works —
when you start a run (POST /v1/agents/{agentId}/runs, /runs/await or /runs/submit). If the connection drops, send the same request again with the same
key: no second run starts and nothing is billed twice. Without the header, every start begins a
new run.
- Same key, same request (same route, agent and body) is a repeat. On
/runs/submitit answers202with the first run'srunId, its currentstatusand itstoolWarnings. On/runsand/runs/awaitit answers409 run_already_startedwithrunId,runStatusand aLocation: read the run withGET /v1/runs/{runId}. The answer is not replayed, because Amy does not store it. mcpAuthis left out of the comparison, so a retry that carries a freshly minted MCP token is still a repeat.- Still starting: while the first request has not created its run yet, a repeat
answers
409 idempotency_in_progresswithRetry-After: 1. Wait, then send it again. If the first request died before creating its run, the key starts a new run once two minutes have passed. - Same key, different request (another route, agent or body) answers
409 idempotency_key_reusedand starts nothing. - A refused start frees its key. A start refused before its run exists (400, 403, 404, 422 or 503) leaves nothing to protect, so fix the request and send it again with the same key.
- Whose keys: your API client's — any of its keys, rotations included — shared by the three start routes. Another client's identical key is unrelated to yours.
- How long: a key lasts 24 hours from its first use. After that the same key starts a new run.
/runs/awaitwith a key lets its run finish when your connection drops, instead of cancelling it, so your retry finds the finished run.- What is stored: a hash of the request and the run id. Never the message, the answer or a token.
- Any string is a key. A UUID is recommended, in either case. Any other string is
compared exactly as you send it, so
Order-1andorder-1are two keys. - A blank key is no key. An
Idempotency-Keyheader that is empty or only spaces counts as not sent: each such start is a new run, billed on its own. - After an erasure: erasing an end user also deletes the keys of their runs, so the same key sent again starts a new run.
- During the deploy that first ships keys: a retry that lands on the older version of Amy still serving is not protected, because that version does not know the key.
- Resume, retry and cancel take no key. They already act once: a second decision
on the same approval, or a second retry while the first is running, is a
409, and cancelling a run (POST /v1/runs/{runId}/cancel) is safe to repeat. Retrying a failed run is capped per run.