Error Handling
All errors from DVARA use a consistent JSON envelope regardless of which surface returned them. Three separate services emit errors, and the tables below are grouped by source so that every row maps to behavior that actually runs:
- Data plane errors — DVARA LLM Gateway on port
8080, serving/v1/chat/completions,/v1/embeddings,/v1/models, and the actuator surface (/actuator/health,/actuator/gateway-status,/actuator/prometheus). - Admin API errors — DVARA Flightdeck on port
8090, serving/v1/admin/*. - MCP Proxy errors — DVARA MCP Proxy on port
8070, serving MCP tool calls. Approval-gate, loop-detection, and other agentic errors share the same envelope shape as the rest of the MCP errors below.
A single error code lives in exactly one of these three tables — there's no global "500-row catalog". If you can't find a code below, it means it isn't reachable on the surface you're looking at.
Error Envelope
{
"error": {
"message": "Human-readable description of the error",
"type": "invalid_request_error | provider_error | gateway_error",
"code": "machine-readable error code",
"param": "field name, if validation error",
"trace_id": "32-character hex trace ID"
}
}
| Field | Type | Description |
|---|---|---|
message | string | Human-readable error description |
type | string | Error category (see table below) |
code | string | Machine-readable error code |
param | string | Field name that caused the error (validation only) |
trace_id | string | 32-character hex trace ID for debugging |
Data plane errors
Emitted by the DVARA LLM Gateway on port 8080 for every /v1/* request.
| Status | type | code | When |
|---|---|---|---|
| 400 | invalid_request_error | validation_error | Missing required field on the request DTO (model, messages). Emitted by request-body validation, before the gateway's exception path. |
| 400 | invalid_request_error | no_provider | No provider on the route matches the requested model, or the matching provider has no credential configured. |
| 400 | invalid_request_error | no_capable_provider | The route has providers but none of them support the requested response_format. |
| 400 | invalid_request_error | invalid_request | Generic request-shape rejection (missing response_format.type, etc.) after the body has parsed successfully. |
| 400 | invalid_request_error | invalid_json | Malformed JSON body — parsing failed before request-DTO binding. Emitted by Spring's HttpMessageNotReadableException handler. |
| 400 | invalid_request_error | unsupported_response_format | Defensive provider-level check for response_format types the provider can't handle. In normal flow, capability-aware routing intercepts these requests first and returns no_capable_provider; this code surfaces only when routing bypasses capability filtering (for example, explicit model-prefix routing). |
| 400 | invalid_request_error | unsupported_capability | The selected provider does not support the content in the request. Raised by Cohere, Groq, and Ollama for image content (vision) and for tool-use / tool-result content blocks. Capability-aware routing does not pre-filter vision or tool-call content, so the provider itself rejects the request. |
| 400 | context_window_error | context_window_exceeded | Estimated input tokens exceed the model's context window and no tenant pruning strategy is configured. |
| 400 | pii_violation | pii_detected | PII detected in the request and the tenant policy is BLOCK. |
| 402 | budget_exceeded | budget_cap_hard | Hard budget cap reached on the tenant or the API key. |
| 402 | token_cap_exceeded | license_token_cap_exceeded | Per-tier monthly token cap exceeded — the entitlement-side counterpart to budget_cap_hard. The response body adds four extra fields (upgrade_url, current_usage, cap, tier) so a customer-facing app can render an upgrade CTA inline without a separate Flightdeck round-trip. |
| 403 | policy_violation | policy_denied | Request blocked by an active policy rule. |
| 403 | guardrail_violation | guardrail_blocked | Guardrail detector flagged the request at or above the configured risk threshold and the tenant action is BLOCK. |
| 403 | data_residency_error | data_residency_violation | Geo-aware routing found no provider in the tenant's allowed regions. |
| 403 | ip_access_error | ip_access_denied | Client IP blocked by global or per-tenant CIDR rule. |
| 403 | tenant_credential_required | tenant_credential_required | Strict-BYOK enforcement rejected the call — the tenant has no own active provider credential. Set under dvara.credentials.require-tenant-credential=true (global) or Tenant.metadata["credentials.require-tenant-credential"] (per-tenant). Audit event: PROVIDER_CREDENTIAL_MISSING. |
| 403 | tenant_cap_exceeded | tenant_cap_exceeded | Per-tier tenant-count cap exceeded — the customer is trying to provision more tenants than their license tier allows. The exception message carries the upgrade URL. |
| 403 | tenant_suspended | tenant_suspended | Tenant administratively suspended — trial-cliff expiry, chronic-abuse sweep, self-delete schedule, or manual operator action. Audit event per blocked request: TENANT_SUSPENDED_BLOCK. |
| 413 | input_size_error | input_too_large | Request exceeds guardrail.max-input-tokens, guardrail.max-messages-per-request, or guardrail.max-message-length. |
| 422 | schema_validation_error | schema_validation_failed | Output schema validation failed after the configured max retries. |
| 429 | rate_limit_error | rate_limit_exceeded | Per-key request count or token budget exhausted in the current 60-second window. Emitted directly by the rate-limit filter. |
| 429 | priority_throttle_error | priority_throttled | Priority admission controller rejected the request because the tenant's tier is over its concurrency threshold. |
| 502 | provider_error | provider_error | Upstream provider returned a non-success status, or the connection failed, after retries and failover. Provider-specific detail is prefixed to the message. |
| 503 | provider_unavailable | provider_circuit_open | The provider's circuit breaker is OPEN; no upstream call was attempted. |
| 503 | provider_unavailable | failover_capability_mismatch | Primary provider failed and no capable fallback exists. Response also carries the X-Gateway-Failover-Blocked: capability_mismatch header. |
Prompt-template codes (data plane)
Emitted when a request references a template via metadata.prompt_template_id and the resolver can't satisfy the reference.
| Status | type | code | When |
|---|---|---|---|
| 400 | invalid_request_error | prompt_template_not_active | Referenced template is not in ACTIVE status. |
| 400 | invalid_request_error | prompt_variable_missing | Required {{variable}} not provided in the request's variables map. |
Additional guardrail error codes
| Status | type | code | When |
|---|---|---|---|
| 403 | guardrail_violation | hallucination_detected | The grounding-detection layer fires with action: BLOCK — response contains ungrounded claims. |
| 500 | guardrail_plugin_error | guardrail_plugin_error | An external guardrail plugin configured with fail-mode: CLOSED failed to complete its call. |
Admin API errors
Emitted by the DVARA Flightdeck on port 8090 for every /v1/admin/* request. Same envelope shape as the data plane, same lowercased-code convention.
Authentication and authorization
| Status | type | code | When |
|---|---|---|---|
| 401 | unauthorized_error | authentication_required | Admin caller missing or invalid Bearer token / PAT / JWT. Emitted by Spring Security's AuthenticationException path. |
| 403 | forbidden_error | access_denied | Tenant-scope violation: a tenant-role caller (admin / developer / viewer) tried to read or write a resource belonging to another tenant, or a caller lacked the URL-pattern RBAC role required for the path. Emits a TENANT_SCOPE_VIOLATION audit event capturing the path, method, requested tenant id, and acting principal — security teams can detect probing without grepping response logs. |
Request validation and parsing
| Status | type | code | When |
|---|---|---|---|
| 400 | invalid_request_error | validation_error | Missing required field on the admin request DTO (e.g. name, email). Emitted by Spring's MethodArgumentNotValidException handler. |
| 400 | invalid_request_error | invalid_json | Malformed JSON request body — parsing failed before DTO binding. |
Not-found codes (all 404 not_found_error)
Every admin resource that supports GET /{id} returns this shape when the ID doesn't exist or belongs to a different tenant than the caller's RBAC scope allows:
tenant_not_found · api_key_not_found · route_not_found · route_version_not_found · policy_not_found · policy_version_not_found · user_not_found · credential_not_found · pricing_not_found · budget_not_found · chargeback_not_found · report_not_found · webhook_not_found · mcp_server_not_found · schema_not_found · session_not_found · prompt_template_not_found · prompt_version_not_found · prompt_experiment_not_found · mock_scenario_not_found · golden_prompt_not_found · drift_report_not_found · eval_prompt_not_found · eval_report_not_found
Request-shape and license-gate codes
| Status | type | code | When |
|---|---|---|---|
| 400 | invalid_request_error | invalid_request | Generic request-shape rejection. |
| 400 | invalid_request_error | invalid_policy_status | Policy status change target is not a valid lifecycle value. |
| 400 | invalid_request_error | invalid_template_status | Template status change target is not a valid lifecycle value. |
| 400 | invalid_request_error | invalid_report_type | Compliance report type is not one of SOC2 / HIPAA / GDPR. |
| 400 | invalid_request_error | compliance_not_available | Compliance reports require an enterprise license key. |
| 400 | invalid_request_error | chargeback_not_available | Chargeback reports require an enterprise license key. |
| 400 | invalid_request_error | mcp_not_available | MCP features require an enterprise license key. |
| 400 | invalid_request_error | encryption_not_configured | DVARA_ENCRYPTION_MASTER_PASSWORD is not set, so credential or guardrail-plugin secrets cannot be encrypted for storage. |
| 400 | invalid_request_error | prompt_experiment_not_running | Experiment is not in RUNNING status. |
| 400 | invalid_request_error | mock_scenario_invalid_name | Scenario filename failed path-traversal validation. |
| 400 | invalid_request_error | mock_scenario_invalid | Scenario Groovy source failed compile-check. |
| 400 | invalid_request_error | invalid_output_schema_scope | Registered output schema must declare a routeId, a modelPattern, or both — neither would never match any request. |
| 400 | invalid_request_error | invalid_policy_dsl | Policy YAML DSL failed compile-check (syntax error, unknown condition, unknown action). |
| 400 | invalid_request_error | invalid_storage_mode | Credential storageMode is not one of ENCRYPTED / REFERENCE. |
| 400 | invalid_request_error | credential_api_key_missing | POST /v1/admin/credentials or rotate with storageMode=ENCRYPTED but no apiKey field in the body. |
| 400 | invalid_request_error | credential_reference_missing | POST /v1/admin/credentials or rotate with storageMode=REFERENCE but no secretReference field in the body. |
| 400 | invalid_request_error | credential_storage_mode_mismatch | Rotate request used the wrong field for the credential's current storage mode (e.g. passed apiKey on a REFERENCE credential). |
| 400 | invalid_request_error | credential_not_rotatable | Tried to rotate a credential that isn't in ACTIVE status (e.g. already REVOKED or SUPERSEDED). |
| 409 | duplicate_error | mcp_server_duplicate | Server ID already exists for the target tenant. |
| 409 | duplicate_error | user_duplicate | User email already exists for the target tenant. |
| 413 | payload_too_large | mock_scenario_too_large | Scenario source exceeded the 256 KB limit. |
| 500 | admin_error | — | Default fall-through for any unmatched gateway-thrown error. |
MCP Proxy errors
Emitted by the DVARA MCP Proxy on port 8070. Every error — auth, policy, agentic governance, upstream failure — comes back through the same lowercase-code, type: "mcp_error" envelope:
{
"error": {
"message": "…",
"type": "mcp_error",
"code": "mcp_server_not_found",
"trace_id": "…"
}
}
| Status | code | When |
|---|---|---|
| 400 | mcp_not_available | MCP Proxy requires an enterprise license. |
| 400 | mcp_pii_detected | PII detected in the tool-call arguments or response, tenant policy is BLOCK. |
| 401 | mcp_auth_required | Missing Authorization: Bearer <key> on the MCP Proxy request. |
| 401 | mcp_auth_invalid | API key does not resolve to an active tenant. |
| 401 | mcp_auth_revoked | API key has been revoked. |
| 403 | mcp_policy_denied | MCP request denied by a policy rule. |
| 403 | mcp_approval_denied | A human reviewer denied the tool call from the DVARA Flightdeck approval queue. |
| 403 | mcp_agent_session_killed | The session has been terminated by POST /v1/admin/sessions/{id}/kill; subsequent tool calls for that session return immediately. |
| 404 | mcp_not_found | Wrong URL on port 8070 (e.g. typo, excluded actuator path, bot probe). Returned by the generic NoResourceFoundException / NoHandlerFoundException handler so probes to non-existent paths don't masquerade as 500 mcp_internal_error. |
| 404 | mcp_server_not_found | MCP server ID not registered for the calling tenant. |
| 408 | mcp_approval_timeout | Approval gate reached its configured timeout with no human decision; tenant default action is deny. |
| 429 | mcp_rate_limit_exceeded | Per-key rate limit exhausted. |
| 429 | mcp_agent_loop_detected | The loop detector fired (repetition threshold, cycle detection, or rate cap) on the session. |
| 502 | (fall-through) | Any other gateway-thrown error code; the MCP Proxy lowercases it, prefixes mcp_ if needed, and returns 502 mcp_error / mcp_<code>. |
| 503 | mcp_server_unavailable | MCP server is suspended, disabled, or the circuit breaker is open. |
| 500 | mcp_internal_error | Any unhandled exception — caught by the generic handler branch. |
Error Examples
Validation Error — Missing Model
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "Hi"}]}'
{
"error": {
"message": "model is required",
"type": "invalid_request_error",
"code": "validation_error",
"param": "model",
"trace_id": "a6783439db1f46a6bfed511a0011e955"
}
}
No Provider Configured
{
"error": {
"message": "No provider configured for model: gpt-4o. Set dvara.llm-gateway.providers.<name>.api-key in application.yml or as an env var.",
"type": "invalid_request_error",
"code": "no_provider",
"trace_id": "d354d2faaa5f4e14939aa8c480fb9d90"
}
}
Unsupported Response Format
{
"error": {
"message": "Ollama provider does not support response_format. Supported formats: [text]",
"type": "invalid_request_error",
"code": "unsupported_response_format"
}
}
Tenant Not Found
{
"error": {
"message": "Tenant not found: t-999",
"type": "not_found_error",
"code": "tenant_not_found",
"trace_id": "e9016783ab4c5d60c3ff042e3h3h7266"
}
}
Credential Not Found
{
"error": {
"message": "Required credential not found: provider.openai.api-key",
"type": "invalid_request_error",
"code": "credential_not_found",
"trace_id": "f1234567890abcdef1234567890abcde"
}
}
Rate Limit Exceeded
HTTP/1.1 429 Too Many Requests
Retry-After: 1
X-Trace-ID: a6783439db1f46a6bfed511a0011e955
{
"error": {
"message": "Rate limit exceeded",
"type": "rate_limit_error",
"code": "rate_limit_exceeded",
"trace_id": "a6783439db1f46a6bfed511a0011e955"
}
}
Provider Error
Error messages include a human-readable hint for common HTTP status codes:
{
"error": {
"message": "Anthropic API error 401 (invalid API key — check your provider credentials)",
"type": "provider_error",
"code": "provider_error",
"trace_id": "b7894561cd2e4f38a1cc820d1f1f5044"
}
}
Hints are appended for these status codes:
| Status | Hint |
|---|---|
| 401 | invalid API key — check your provider credentials |
| 403 | access denied — your API key may lack required permissions |
| 404 | model or endpoint not found — check the model name |
| 429 | rate limited by provider — too many requests |
| 500 | provider internal server error |
| 502 | provider returned bad gateway |
| 503 | provider is temporarily unavailable |
Circuit Breaker Open
When a provider fails repeatedly, the gateway stops sending requests to it temporarily to avoid cascading failures. The error message explains what happened and what to do:
{
"error": {
"message": "Provider anthropic is temporarily unavailable — too many recent requests failed, so new requests are paused. This usually resolves automatically after a short cooldown. If it persists, check your provider API key and account status.",
"type": "provider_unavailable",
"code": "provider_circuit_open",
"trace_id": "c8905672de3f5049b2dd931e2g2g6155"
}
}
PII Detected (Block Mode)
{
"error": {
"message": "PII detected in request — 2 entity type(s) found. Tenant policy: BLOCK",
"type": "pii_violation",
"code": "pii_detected",
"trace_id": "g0127894ab5c6d71e4ff153f4i4i8377"
}
}
Guardrail Blocked
{
"error": {
"message": "Request blocked: guardrail violation detected (INJECTION, JAILBREAK)",
"type": "guardrail_violation",
"code": "guardrail_blocked",
"trace_id": "h1238905bc6d7e82f5gg264g5j5j9488"
}
}
Input Too Large
{
"error": {
"message": "Request exceeds maximum messages limit: 150 > 100",
"type": "input_size_error",
"code": "input_too_large",
"trace_id": "i2349016cd7e8f93g6hh375h6k6k0599"
}
}
Schema Validation Failed
{
"error": {
"message": "Output schema validation failed: $.name: required property missing",
"type": "schema_validation_error",
"code": "schema_validation_failed",
"trace_id": "j3450127de8f9004h7ii486i7l7l1600"
}
}
Context Window Exceeded
{
"error": {
"message": "Estimated token count 150000 exceeds context window 128000 (no pruning strategy configured)",
"type": "context_window_error",
"code": "context_window_exceeded",
"trace_id": "k4561238ef9g0115i8jj597j8m8m2711"
}
}
Failover Blocked
HTTP/1.1 503 Service Unavailable
X-Gateway-Failover-Blocked: capability_mismatch
{
"error": {
"code": "failover_capability_mismatch",
"message": "Failover blocked: no fallback provider supports response_format: json_schema. Primary provider [openai] failed: upstream down"
}
}
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
400 no_provider | API key not set for the provider | Set the environment variable (e.g., OPENAI_API_KEY) |
403 data_residency_violation | Data residency policy violation | Configure routing to use providers in allowed regions |
400 unsupported_response_format | Provider doesn't support the format | Use a different provider or remove response_format |
400 no_capable_provider | No provider on the route supports it | Add a capable provider to the route |
404 tenant_not_found | Tenant ID does not exist | Verify the tenant ID via GET /v1/admin/tenants |
404 route_not_found | Route ID does not exist | Verify the route ID via GET /v1/admin/routes |
404 route_version_not_found | Route version does not exist | Check version history via GET /v1/admin/routes/\{id\}/versions |
404 report_not_found | Report ID does not exist | Verify the report ID via GET /v1/admin/reports |
400 pii_detected | PII found and action is BLOCK | Remove PII from request, or change tenant PII action to REDACT or LOG |
403 guardrail_blocked | Injection/jailbreak/content detected | Review prompt content; change tenant guardrail action to FLAG or LOG |
413 input_too_large | Request exceeds input size limits | Reduce message count, message length, or total input tokens; increase tenant limits via metadata |
422 schema_validation_failed | Response doesn't match JSON schema | Review the registered schema via GET /v1/admin/schemas and tighten or relax it; the registry does not yet retry on a single failure (maxRetries is reserved for a future correction-prompt flow). |
400 context_window_exceeded | Context window full, no pruning | Reduce conversation length; configure pruning strategy via tenant metadata (guardrail.context.pruning-strategy) |
402 budget_cap_hard | Hard budget limit exceeded | Wait for next budget period or increase budget via PUT /v1/admin/budgets/\{id\} |
400 compliance_not_available | Enterprise license not active | Set DVARA_LICENSE_KEY to a valid enterprise license key |
400 invalid_report_type | Invalid report type specified | Use one of: SOC2, HIPAA, GDPR |
400 mcp_not_available | MCP features require enterprise | Set DVARA_LICENSE_KEY to a valid enterprise license key |
401 mcp_auth_required | Missing Bearer token on MCP Proxy | Include Authorization: Bearer gw_<key> header |
401 mcp_auth_invalid | Invalid API key on MCP Proxy | Verify the API key is correct |
401 mcp_auth_revoked | API key revoked on MCP Proxy | Generate a new API key |
404 mcp_server_not_found | MCP server ID does not exist | Verify server ID via GET /v1/admin/mcp/servers |
409 mcp_server_duplicate | Server ID already exists for tenant | Use a different server_id or check existing servers |
403 mcp_policy_denied | MCP request denied by policy | Check policy rules via GET /v1/admin/policies. Review mcp_server, mcp_tool, mcp_arg conditions. |
503 mcp_server_unavailable | MCP server suspended/disabled | Re-activate the server via PUT /v1/admin/mcp/servers/\{id\}, or check upstream health via /v1/admin/mcp/servers/\{id\}/health |
429 rate_limit_exceeded | Too many requests | Wait for Retry-After seconds, or increase limits |
502 provider_error | Upstream provider returned an error | Read the hint in the error message (e.g., "invalid API key", "rate limited"). For 401: check your provider API key env var. For 429: reduce request rate. For 5xx: check the provider's status page. The gateway retries automatically. |
503 provider_circuit_open | Too many recent provider failures | The gateway pauses requests to a failing provider to prevent cascading errors. It resolves automatically after a short cooldown. If it persists after restarting: check your provider API key and account status. |
503 failover_capability_mismatch | Primary failed, no capable fallback | Add more providers to the route that support the capability |