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 singleerror 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
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: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)
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)
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 carriescode: "response_blocked" with the same fields as request_blocked:
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":
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: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. Withfetch in Node.js the socket error is on err.cause:
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