Developer docs

Rate limits & idempotency

Per-key rate limits with standard headers, and Idempotency-Key support so retries never double-send or pay twice for an AI draft.

Two request-level features make automating against the API safe: rate limits tell you how much budget you have, and idempotency lets you retry sending and AI endpoints without sending or paying twice.

Rate limiting

Each API key is limited to 300 requests per 60 seconds (5 req/s sustained). Every response carries the current budget:

HeaderMeaning
X-RateLimit-LimitThe per-window ceiling.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetSeconds until the window frees a slot.

Going over the limit returns 429 Too Many Requests with a Retry-After header (in seconds). Wait that long, then retry.

Search capacity

Searches share a separate concurrency budget to leave database capacity for replies and background delivery. A search can return 503 Service Unavailable with Search is busy. Try again shortly. even when your request-rate budget remains available. Wait briefly, then retry with fewer searches in parallel. This response means the search did not complete; it does not mean there are no matches.

The AI endpoint has its own, much lower limit

POST /conversations/:id/ai-draft is additionally limited to 20 requests per 60 seconds per key, reported under its own headers so the general budget above stays readable:

HeaderMeaning
X-RateLimit-AI-LimitThe per-window ceiling for AI calls.
X-RateLimit-AI-RemainingAI calls left in the current window.
X-RateLimit-AI-ResetSeconds until that window frees a slot.

It is separate because this endpoint spends money, not just a database round trip. At the general ceiling a runaway loop would bill three hundred generations a minute while looking completely healthy — every request valid, nothing to alarm on. Your workspace AI budget is still the real backstop; this is the earlier, cheaper one.

Both limiters run on an AI request, and the stricter one bites first.

Idempotency

Creating a conversation and replying both send email, so a blind retry after a network blip could send twice. Prevent that by sending an Idempotency-Key header — any string unique to the operation (a UUID is ideal):

POST /conversations/:id/reply
Authorization: Bearer rp_live_…
Idempotency-Key: 3f9c1e7a-9b2d-4c8e-a1f0-6d5b4c3a2e10
Content-Type: application/json
  • The first request runs normally; its response is stored against the key.
  • A retry with the same key and body replays that stored response — no second email.
  • Reusing the key with a different body returns 409 Conflict.
  • A retry sent while the first is still in flight returns 409 — wait, then retry.

Keys are remembered for 24 hours. A request without the header is processed normally (not deduplicated), so always send one whenever a retry is possible.

AI drafts

POST /conversations/:id/ai-draft takes the same header, because a generation spends your AI budget and keeps running after your client gives up. The rules above apply, and a replay neither generates nor saves the draft again. Three things differ:

  • A retry while the first call is still running gets 409 with code: "AI_REQUEST_IN_PROGRESS". Wait a few seconds, then retry with the same key to collect the result.
  • A call that failed releases its key, so the retry runs again. If the draft was already generated (the save failed, say), the retry gets that draft back instead of paying for another.
  • Keys belong to the API key that sent them, so two integrations never collect each other’s drafts.

Recovery after an interrupted request (next grouped release)

For conversation creation and replies, an accepted message source is retained with the idempotency reservation. If the connection is lost before a response is saved, retry using the same key and unchanged body. The retry can return the existing conversation and its current message state without creating or sending another message. A conversation response can contain queued or uncertain delivery; check the message’s deliveryState before treating it as delivered.

Completed responses keep the normal 24-hour replay window. An unresolved committed source is retained beyond that window until reconciled. A missing retained message returns 409 and is not recreated. A request that committed no source can safely retry after its reservation is released or expires. Older uncertain requests without a recoverable source still return 409; inspect the conversation before starting a separate send.

A retained source does not imply successful provider delivery; inspect the existing outcome before taking further action.