Rate limits & error handling
Every limit, every error code, and how to back off cleanly
Prowl throttles the MCP endpoint at two layers: a pre-auth cap of 300 requests/min per client IP (MCP_PREAUTH_IP_LIMIT_PER_MIN) applied before your key is even validated, and a 120 requests/min per API key cap (MCP_RATE_LIMIT_PER_MIN) after auth. Both use a 60-second sliding window and return HTTP 429 with JSON-RPC error code -32002. Auth failures return 401 / -32001, IP-allowlist rejections return 403 / -32003, and anything unexpected returns -32603. Per-tool problems come back as an Error: string inside a normal tool result, not as a protocol error.
Two things error out in two different ways
It helps to keep these separate:
- Transport / auth errors — a bad key, a rate-limit breach, or a blocked IP are rejected by the MCP middleware before your call runs. You get a real JSON-RPC 2.0 error body with the codes in the table below.
- Tool-call errors — insufficient wallet balance, a key scope denial, a spend-cap hit, an oversized payload, or an unknown tool name are returned as a successful tool response whose text starts with
Error:. The JSON-RPC call succeeds; the content tells you what went wrong.
So your client should check both the JSON-RPC error field and whether a tool result string begins with Error:.
Rate-limit table
| Surface | Limit (default) | Window | Scope | Response |
|---|---|---|---|---|
| MCP endpoint, pre-auth | 300 / min | 60s sliding | per client IP | 429, JSON-RPC -32002 |
| MCP endpoint, per key | 120 / min | 60s sliding | per API key | 429, JSON-RPC -32002 |
| Login | 10 | 60s | per IP | 429 + Retry-After |
| Register | 5 | 3600s | per IP | 429 + Retry-After |
| Billing (checkout, top-up, verify, change, cancel) | 5 | 60s | per user | 429 + Retry-After |
| Schedule webhook trigger | 6 | 60s | per webhook token | 429 + Retry-After |
| Admin write endpoints | 30 | 60s | per user | 429 + Retry-After |
All windows are sliding buckets and are shared across server workers when Redis is configured. Every MCP and web limiter is env-tunable except the billing (5) and admin-write (30) limits, which are hardcoded.
Handling a 429 (backoff guidance)
How you back off depends on which layer threw the 429:
- Web REST 429s (login, register, billing, webhook, admin, OAuth, key-cap) always include a
Retry-Afterresponse header. Its value is the window upper bound in whole seconds. Honor it: wait that many seconds before retrying. - MCP transport 429s (-32002 from the pre-auth IP cap or the per-key cap) do not carry a
Retry-Afterheader. Use exponential backoff with jitter — start around 1s, double each retry, cap the delay, and add randomness so retries don't synchronize. The window is 60 seconds, so a breach clears within a minute.
Best practice: keep steady-state traffic under 120 requests/min per key and 300 requests/min per IP, serialize bursts, and treat any 429 as a signal to slow down.
JSON-RPC error codes (transport / auth layer)
| JSON-RPC code | HTTP | Meaning | Example message |
|---|---|---|---|
| -32001 | 401 | Auth failure | "Missing or invalid Authorization header. Use: Bearer prowl_<your_key>" / "Invalid or revoked API key." |
| -32002 | 429 | Rate limit exceeded | "Rate limit exceeded for this API key. Max 120 requests/min. Slow down and retry." |
| -32003 | 403 | IP not allowlisted | "API key is not allowed from this IP address." |
| -32603 | 500 / 503 | Internal / fallback error | generic internal error |
Every 401 also returns a WWW-Authenticate: Bearer realm="prowl-mcp", resource_metadata="<PRM URL>" header (RFC 9728) so OAuth-capable clients can auto-discover the authorization server.
Common tool-call errors
These arrive as the text of a normal tool result (prefix Error:), so read the string, not the JSON-RPC error field:
| Situation | What you get back |
|---|---|
| Insufficient wallet balance | "Error: Insufficient wallet balance. Need $X available, have $Y. Top up credits in MCP Home or subscribe at prowl.chat." |
| Key scope denied | "Error: API key is not allowed to call tools in category '<category>'. Tool '<tool_name>' was blocked by key scope policy." |
| Per-key spend cap hit | "Error: API key spend limit reached (<reason>). Top up your wallet or use a key with a higher limit." |
| Params too large (prowl_call_tool) | "Error: params too large (<n> bytes); max 65536 bytes." |
| Tool not found | "Error: Tool '<tool_name>' not found. Use prowl_list_tools to see available tools." |
| Tool not found (prowl_tool_info) | "Error: Tool '<tool_name>' not found. Available tools (first 20): … Use prowl_list_tools to see the full catalog." |
| Internal error | "Error: Analysis failed due to an internal error. Please retry. (ref: <id>)" — quote the ref: id to support |
| Billing DB outage, fail-closed | "Error: Billing is temporarily unavailable. Please retry in a moment." |
Billing fails closed by default: during a database outage Prowl refuses paid work rather than handing out free runs. When a tool succeeds but the charge can't be fully collected, the result still returns your data plus a warning, and you are not double-charged.
Debug after the fact with prowl_get_error_feed
For a retrospective view of what failed on your account, call prowl_get_error_feed. It is read-only, scoped to your own account, and returns recent failed tool calls, billing events, and actions.
- hours — lookback window, default 24, max 720.
- limit — max rows, default 50, max 200.
- source —
tool_error,action,tool_invocation, orbilling_outbox. - error_class —
network,server,auth,rate_limit,timeout,plan_denied,client, orunknown. - severity —
alert,warn, orinfo; also filter bytool_name.
Frequently asked questions
What are Prowl's default MCP rate limits?
300 requests per minute per client IP before authentication (MCP_PREAUTH_IP_LIMIT_PER_MIN) and 120 requests per minute per API key after authentication (MCP_RATE_LIMIT_PER_MIN). Both use a 60-second sliding window and return HTTP 429 with JSON-RPC error code -32002.
How should I handle a 429 response?
Web REST endpoints (login, register, billing, webhooks, OAuth) include a Retry-After header in whole seconds - wait that long, then retry. The MCP transport 429 has no Retry-After header, so use exponential backoff with jitter starting around 1 second; the 60-second window clears the breach within a minute either way.
What do the JSON-RPC error codes mean?
-32001 is an auth failure (HTTP 401), -32002 is a rate-limit breach (HTTP 429), -32003 is an IP-allowlist rejection (HTTP 403), and -32603 is an internal error (HTTP 500/503). Auth and IP errors are thrown by the middleware before your tool runs.
Why did my tool call return an 'Error:' string instead of failing?
Per-tool problems - insufficient wallet balance, key scope denial, spend-cap hit, oversized params, or an unknown tool name - are returned as the text of a successful tool result that starts with 'Error:'. Only transport and auth problems come back as real JSON-RPC error codes.
How do I see what failed on my account?
Call prowl_get_error_feed. It is read-only and scoped to your account, returning recent failed tool calls, billing events, and actions. Filter by hours (default 24, max 720), source, error_class, severity, or tool_name, and quote any 'ref:' id when contacting support.