Questions · Updated 2026-09-28
What does rate_limit_exceeded mean?
rate_limit_exceeded means the request limiter refused this request because too many arrived from this API key, or from this IP address, inside the current window; it is retryable, so wait the seconds in Retry-After and send the same request again. Every response, POST /emails included, carries ratelimit-limit, ratelimit-remaining and ratelimit-reset so a client can slow down before it is refused.
rate_limit_exceeded means the request limiter refused this request: too many requests arrived from this API key, or from this IP address, inside the current window. Waiting cures it: retryable is true, so the response carries retry_after_seconds and a Retry-After header. Wait that long, then send the same request again, whether it was POST /emails or any other call. The error catalogue message is "Too many requests."
How long to wait
The catalogue fix is "Back off and retry honoring the Retry-After header." The API contract describes that header as "Seconds to wait before retrying. Present on every refusal the catalog marks retryable — and deliberately absent on one it does not, because a refusal waiting cannot cure must never hand a client a wait hint." retry_after_seconds in the body is the same number: the seconds left in the current window. The correct retry is the identical request, with the same Idempotency-Key if it carried one, sent once after the wait. Budgets and the kill switch puts the rule in one line: "a backoff you guessed is how a backoff becomes a hot loop."
What the limiter counts
The limiter counts requests, not messages, and every request counts: a list, a read of one message, a preflight, a send. An authenticated key has a window of 60 seconds and 600 requests in it. A request that carries no key counts against a window for the caller's IP address, and a request whose key fails to authenticate shares that IP window, so once that window is spent a flood of bad credentials is refused for too many requests rather than as a bad key.
Slowing down before the refusal
Every response carries ratelimit-limit, ratelimit-remaining and ratelimit-reset. The contract describes them as "Messages this API key may spend in one 60-second window.", "Messages left in the current window." and "Seconds until the current window resets and the budget refills." The trio reports the tighter of the request limiter and the key's own send window, so a client that stops when ratelimit-remaining reaches zero and resumes after ratelimit-reset seconds does not meet this code. GET /limits/keys/{id} returns rate_ceiling_per_minute, consumed_in_window and rate_window_started_at for one key's send window, and GET /usage returns consumed_in_window for every key.
curl -sS -X GET https://api.agentisend.com/limits/keys/9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42 \
-H "Authorization: Bearer $AGENTISEND_API_KEY"200
{
"api_key_id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
"budget_per_period": 1,
"consumed_in_period": 1,
"consumed_in_window": 1,
"paused": true,
"paused_at": "2026-09-04T09:14:00Z",
"paused_reason": "string",
"period": "hourly"
}The per-key ceiling
rate_ceiling_exceeded is the key's own per-minute send ceiling, the rate_ceiling_per_minute a person set with PATCH /limits/keys/{id}. Its catalogue message is "Per-minute rate ceiling for this key is exhausted." and its fix is "Wait the seconds below and send the same request again. Raising the ceiling is a person’s decision, made in the console; a key cannot raise its own." It counts sends rather than requests, it is also retryable with retry_after_seconds and a Retry-After header, and an API key may lower it but not raise it. Set it on an agent's key below the request limiter's number and a runaway sender meets your ceiling first, under a code that names the key.
Read retryable before sleeping. trust_throttled carries the same status, waiting does not cure it, and it carries no wait hint.