Handling Policy Blocks and Gateway Errors
Gateway-generated failures use a consistent public error envelope. An upstream provider response may instead be preserved so that its status and provider-specific details are not lost. This page separates the stable Keeptrusts errors from those provider responses.
Public error envelope
Keeptrusts returns JSON in this shape:
{
"error": {
"message": "Request blocked by policy: prompt_injection.detected",
"type": "invalid_request_error",
"param": null,
"code": "content_policy_violation"
}
}
Use error.type and error.code for program logic. Treat error.message as user-facing text that may be more descriptive.
Common error categories
| Situation | Status | error.type | error.code |
|---|---|---|---|
| Buffered request or response blocked by policy | 400 | invalid_request_error | content_policy_violation |
| Header/body session ID mismatch | 422 | invalid_request_error | session_id_mismatch |
| Missing, invalid, revoked, or expired API token | 401 | authentication_error | invalid_api_token or tokens.expired |
| Token budget exhausted | 402 | cost_budget_exceeded | tokens.budget_exhausted |
| Token request limit exhausted | 403 | access_denied | tokens.request_limit_exhausted |
| Gateway-generated provider failure | route-specific 4xx or 5xx | provider_error | may be null |
| Gateway runtime failure | 500 | internal_error | may be null |
Policy block example
For a non-streaming request, a policy block returns HTTP 400 and the standard envelope:
{
"error": {
"message": "Request blocked by policy: prompt_injection.detected",
"type": "invalid_request_error",
"param": null,
"code": "content_policy_violation"
}
}
Handle this case as a governed refusal, not as a transport failure.
For a streaming request, the HTTP stream may already have started before an output policy blocks the response. In that case the status remains 200 and the policy error is delivered in the SSE body. Inspect the stream events instead of relying only on the initial status.
Invalid request example
Validation failures usually mean the payload shape or request options are wrong:
{
"error": {
"message": "Body session_id must match the configured session header when both are present",
"type": "invalid_request_error",
"param": null,
"code": "session_id_mismatch"
}
}
Fix the request and retry only after the client payload is corrected.
Provider failures
Provider failures are deliberately less uniform. Depending on the request family and where the failure occurs, the gateway either preserves the upstream provider's status and error body or creates a provider_error envelope. Do not assume a provider-specific message or code will be present.
Treat a provider failure as retryable only when the status is retryable, the operation is idempotent, and the surrounding workflow can tolerate delay. Never turn a failed provider call into a silent success.
How to debug the failure
- Reproduce the request while
kt events tail --since 10m --jsonis running. - Narrow to blocked decisions with
kt events tail --since 10m --verdict blocked --jsonwhen the failure is policy-related. - Treat the matching Event and request ID as authoritative. Open History only when capture is enabled and you need retained request/session detail.
- Use Trail or exports when the failure needs an evidence package for handoff.
Application guidance
- Branch on
error.typeanderror.code, not on free-form message text. - Do not retry
invalid_request_errororcontent_policy_violationblindly. - For provider errors, consider both the HTTP status and the returned body; the body may be provider-native.
- Retry only when the request is idempotent and your caller can tolerate delay.
- Log the request identifier and the returned error fields together so later investigation can join application logs to Keeptrusts evidence.