Skip to main content
Branch on error.code. It is the machine-readable identifier and it is stable; message is for humans and can change. Every code on this page is checked against the codes the API actually emits, so a code you find here is one you can receive.

The error envelope

Every error is a JSON object with a single error key:
Some codes add fields of their own, at the top level of error — there is no nested details object. retry_after, for example, is error.retry_after. The fields each code adds are shown in its section below. One exception: a request body that fails schema validation on an endpoint with a typed body (for example POST /api/v1/guard without messages) is rejected with HTTP 422 and FastAPI’s {"detail": [...]} body, which has no error key. Treat any 422 as a bug in the request, not something to retry. To correlate a failure with our logs, quote the X-Request-ID response header. Security decisions also carry X-PromptGuard-Event-ID.

Error code reference

Every 429 code shares one status. A client that retries every 429 will retry subscription_inactive forever, and will hammer a spent quota until the next billing month. Branch on code before you back off.

Authentication and authorization

Missing or invalid key (401)

missing_api_key, disabled_api_key and expired_api_key have the same shape. None is fixed by retrying: check that PROMPTGUARD_API_KEY is set, that the key starts with pg_live_, and that it is still enabled and unexpired in the dashboard. After a key rotation, expired_api_key means the old key’s grace window has closed. On the proxy, an invalid_api_key whose message names a provider (for example “Incorrect Anthropic API key provided”) means the provider rejected the provider key configured on your project, not your PromptGuard key.

Scope denied (403)

A key created with restricted scopes, or a scan-only device credential, is refused on endpoints outside those scopes:
The response does not say which scope was missing. Create a key with the scope the endpoint needs, or use an unrestricted key.

Rate limits and quotas

Rate limit exceeded (429)

retry_after is in seconds and matches the Retry-After header. When the provider rate-limits a proxied request, the code is the same but type is rate_limit_error, there is no retry_after in the body, and the provider’s own Retry-After header is passed through — so read the header first.

Monthly quota exceeded (429)

Here retry_after is the number of seconds until the quota resets at the start of the next month — often days. Surface upgrade_url or on_demand_url instead of sleeping. on_demand_url is present only on plans that sell pay-as-you-go; on Free it is absent and the message names only the upgrade.

Spending limit exceeded (429)

Subscription inactive (429)

There is deliberately no retry_after: waiting does not fix this. Nothing changes until the subscription is reactivated at reactivate_url. subscription_status is none when no plan covers the key’s organization at all: a provider answered for its bill and no longer does, and the organization has no owner of its own. Nothing at reactivate_url changes that one. Contact support.

Retrying correctly

monthly_quota_exceeded, spending_limit_exceeded and subscription_inactive are left out on purpose: each needs a person, not a retry. So is bot_detected: it carries retry_after, but an automatic retry loop is the behaviour that was flagged, so surface it instead.

Security blocks

Request blocked (403)

When the input scan blocks a proxied request, PromptGuard returns HTTP 403:
dashboard_url is present when the dashboard URL is configured. The threat category is not in the body; read the X-PromptGuard-Threat-Type and X-PromptGuard-Confidence response headers, and open the event in the dashboard under Projects → Interactions for the full audit record. The Guard API (POST /api/v1/guard) does not use this envelope: a blocked decision there is a normal 200 response with decision: "block".

Blocked responses

When the output scan blocks a non-streamed completion, the body carries code: "response_blocked" with the same fields as request_blocked:
This body is currently delivered with HTTP 200, not 403. A client that only inspects the status code will treat it as a completion with no choices. Check for error in every successful proxy body.

Blocked streamed responses

With "stream": true, the output scan runs on the full accumulated body before any byte is emitted. There is no status code left to change by then, so a block is delivered as a terminating SSE error event — and it carries type: "content_filter" with code: "content_blocked":
No part of the completion is written when this happens, so the event is the whole response. Branch on all three block codes:
Show the end user a neutral message and log event_id. Do not try to “clean” a blocked prompt with your own regexes and resend it: a rewrite that slips past the scan is exactly the evasion the scan exists to catch.

Detection unavailable (503)

A project configured to fail closed refuses to forward a request the scan could not evaluate:
This is our outage, not a verdict on your content. Retry with backoff.

Provider errors

On the proxy, an error from the upstream provider is passed through with the provider’s status code and PromptGuard’s envelope:
Retry only the 5xx provider_error. The provider’s request id and rate-limit headers are passed through, so quote the provider’s request id to their support.

Unexpected errors (500)

error_id equals the X-Request-ID header. Quote it to support.

Network errors

Connection failures never reach PromptGuard, so they carry no envelope. With fetch in Node.js the socket error is on err.cause:
Retry these with exponential backoff, the same as a 5xx.

Testing your error handling

Build fixtures from the real envelopes above rather than inventing codes, so a test cannot pass against a code the API never sends:

Next Steps

Best Practices

Production deployment and reliability best practices

Rate Limits

Understanding and managing API rate limits

Troubleshooting

Common issues and debugging techniques

Monitoring

Set up comprehensive monitoring and alerts
Last reviewed 2026-09-16.