Questions · Updated 2026-09-28
How do I handle rate limits?
Handle rate limits by reading ratelimit-limit, ratelimit-remaining and ratelimit-reset on every response, and when a call is refused for too many requests, waiting the seconds in Retry-After before sending the same request again. PATCH /limits/keys/{id} sets a lower per-key ceiling; only a person can raise one.
Handle rate limits by reading three headers that every response carries: ratelimit-limit, ratelimit-remaining and ratelimit-reset. When ratelimit-remaining reaches zero, wait ratelimit-reset seconds before the next call. When a call is refused for too many requests, the body's retry_after_seconds and the Retry-After header say how long to wait, and the correct retry is the same request, sent once after that wait. A new key's ceiling is 600 requests in a 60-second window.
The headers
The API describes them in its own words. ratelimit-limit: "Messages this API key may spend in one 60-second window." ratelimit-remaining: "Messages left in the current window." ratelimit-reset: "Seconds until the current window resets and the budget refills." The reset value is never zero, so a client that sleeps for it never spins. The trio reports the tighter of two windows, the key's per-minute send ceiling and the per-request limiter, so the headers warn before a refusal arrives. GET /limits/keys/{id} shows the same window as rate_ceiling_per_minute and consumed_in_window, and GET /usage shows it for every key on the account.
The two refusals
rate_ceiling_exceeded is the key's own ceiling. Its message names how many messages the key sent this minute and what its ceiling is, and the catalogue 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." rate_limit_exceeded is the per-request limiter: the message is "Too many requests." and the fix is "Back off and retry honoring the Retry-After header." Both carry retry_after_seconds, which the schema says is "Present exactly when retryable is true, absent otherwise.", and a Retry-After header.
Not every refusal that shares this status is a rate limit. daily_quota_exceeded, monthly_quota_exceeded and trust_throttled carry the same status with retryable false, and the retryable field's description says why: "Whether repeating the identical request could succeed later. A 429 that waiting cannot cure (daily quota, trust throttle) is false." Read retryable before sleeping, and when it is false, stop and read fix. A budget refusal, agent_budget_exceeded, is a different thing and is not retryable either; it is the subject of agent email budgets.
curl -sS -X PATCH https://api.agentisend.com/limits/keys/9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42 \
-H "Authorization: Bearer $AGENTISEND_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"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"
}Setting a lower ceiling
PATCH /limits/keys/{id} takes rate_ceiling_per_minute. The summary is "Set or clear this key’s period budget and per-minute ceiling. An API key may lower its own; raising one is a person’s decision, made in the console." Give each sender its own key from POST /api-keys and set the ceiling to the rate you intend, so a runaway loop meets rate_ceiling_exceeded at your number rather than the default. Send only the field you want to change.
Send the retry with the same Idempotency-Key as the refused call. A refusal ran nothing, so the key is free; and if an earlier attempt did go through and its answer was lost, the key replays that answer instead of sending again. That is why both fixes say to send the same request, not a new one.