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

WhoStandard limitCounted per
A request with a valid API key300 requests a minuteYour API client — every key of your organisation shares one allowance, rotated keys included.
A request with no key, or a wrong, revoked or expired one30 requests a minuteThe 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, then 429s once that allowance is used up. If you get 429s straight after a rotation, check which key the caller is using.

Handling a 429

429 application/problem+json
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.

TypeScript
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

WhatLimitWhen exceeded
JSON request body1 MB413 "Payload too large."
message8,000 characters, not empty400
endUserRef200 characters; printable ASCII with no spaces400
tools32 names per run, each up to 100 characters400
mcpServers (inline)16 per run; 1–64 tools each400
mcpAuth16 per run; each token up to 8,192 characters400
attachmentFileIds8 per run400
One uploaded file (POST /v1/files)25 MB413 (a file only just over the limit can get a 400)
An attached image or audio file20 MB; images PNG, JPEG, WebP or GIF; audio MP3, MP4, WAV, WebM, Ogg or FLAC400 naming what was wrong, when you attach it
An attached PDF20 pages and 20 MB; counts as one attachment400 naming what was wrong, when you attach it
History replayed with threadConversationHistorythe newest 20 runs and 48,000 charactersOlder 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.

WhatDefault
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 run8 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 summaries3 per run
Images fetched from the web4 per run
Spreadsheet calls3 per run; up to 5 sheets of 10,000 rows each
Specialists working in parallel · delegation depthup 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:

403 application/problem+json
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

LimitFreeStarterPro
Tokens a month (Amy-funded)100,0005,000,000100,000,000
Live API keys21050
File storage100 MB1 GB10 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/submit it answers 202 with the first run's runId, its current status and its toolWarnings. On /runs and /runs/await it answers 409 run_already_started with runId, runStatus and a Location: read the run with GET /v1/runs/{runId}. The answer is not replayed, because Amy does not store it.
  • mcpAuth is 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_progress with Retry-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_reused and 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/await with 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-1 and order-1 are two keys.
  • A blank key is no key. An Idempotency-Key header 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.

Next

  • Errors — every status and code.
  • Usage — reading what a run cost.