Limits, errors and retries
Calls and credits
| Limit | Applies to | Ceiling |
|---|---|---|
| Request rate | Per key or OAuth connection, across REST and MCP | 60 calls per minute by default |
| Paid starts | Per account, shared with app story and book starts | 20 per 10 minutes |
| Monthly credit cap | Each key or connection | The cap you choose, per UTC calendar month |
| Credit balance | Your account | Your available credits |
| Keys and connections | Per reader | 20 at once |
| Bad bearers | Per address | 30 per minute |
| OAuth registration | Per address | 20 per hour |
| OAuth token exchange | Per address | 60 per minute |
Free accounts also share a daily Free budget, which resets at midnight UTC. The API keeps the app's plan rules. A key cap reserves the whole known writing and audiobook price before work starts. A cap refusal or insufficient balance returns 402, not 429. Raising a cap does not buy credits. Caps can be set from 0 to 100,000 credits. An uncapped key is also supported, but an admin key must have an explicit cap before it can spend. Choose a cap for every spending integration.
For rate limits, HTTP 429 includes Retry-After in whole seconds and retryAfterSeconds in the body. Wait at least that long. MCP tool errors put the wait in their text.
Error format
{"error":"this key is limited to 60 calls a minute","code":"rate_limited","retryAfterSeconds":30}Show error to the reader and branch on code when present. Current validation failures can return {error, issues} without code. Unexpected server failures can return only {error}. Always check the HTTP status too. OAuth and MCP JSON-RPC have their own protocol error shapes.
| HTTP | Code | What to do |
|---|---|---|
| 400 | invalid_request | Correct the input. Validation issues can name the field. Do not repeat unchanged. |
| 401 | unauthorized | Check the bearer or sign in again. Revoked or expired credentials cannot be retried unchanged. OAuth clients follow WWW-Authenticate. |
| 402 | payment_required | Check balance and key cap. Add credits or deliberately adjust the cap before trying again. |
| 403 | forbidden | Check scope and plan rules. Do not retry unchanged. |
| 404 | not_found | Check ID and early-access eligibility. Other readers' IDs also return 404. |
| 409 | conflict | Wait for the existing job or resolve its state before retrying. |
| 413 | too_large | Reduce the request or result size. |
| 429 | rate_limited | Wait the Retry-After seconds. |
| 503 | unavailable | A switch, vendor budget or service is unavailable. Wait and retry safe operations with backoff. |
| Other 4xx | refused | A refusal without a more specific code. Show the error and correct the cause. |
| Other 5xx | server_error | Retry safe operations with bounded exponential backoff and jitter. Code may be absent. |
Safe retries
List, export, balance, levels and quote calls can be repeated. Start with a short delay, increase it after failures, add random jitter and stop after a few attempts. Always respect Retry-After.
Story status is not a purely read-only operation. If an audiobook was requested, the first status read after writing starts it. Concurrent status reads claim that start once. Reuse the same story ID and poll about every 20 seconds until a terminal status.
There is no supported Idempotency-Key header for creating stories. Repeating POST /api/v1/stories or create_story can create another charge. After a lost response or timeout, check the app for existing work before starting again. Never automatically retry an uncertain paid creation. Keep any returned ID and resume status checks instead.
An error does not mean a completed writing charge is refunded. Failed books use the app's refund rules. For a disputed charge, send the endpoint, story ID and time through support, without secrets or story text.