Error Handling
Available with no license, in the Development posture. A license adds the MCP and A2A planes, production rights and support — not this feature.
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). - Console and Portal errors — DVARA Flightdeck on port
8090, serving the Console and the workspace Portal. - MCP Gateway errors — served by the gateway on port
8080alongside/v1/*(the separate MCP process was retired in 1.7.0). 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 when the JSON body is malformed or unparseable. |
| 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 workspace pruning strategy is configured. |
| 400 | pii_violation | pii_detected | PII detected in the request and the workspace policy is BLOCK. |
| 402 | budget_exceeded | budget_cap_hard | Hard budget cap reached on the workspace or the API key. |
| 402 | budget_exceeded | per_call_cost_exceeded | A single request's estimated cost (max_tokens × output price) exceeded the per-call ceiling, set by dvara.llm-gateway.finops.per-call-max-cost-usd or Workspace.metadata["cost.per-call-max-usd"]. Complements the accumulated-spend cap; off by default. |
| 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 workspace action is BLOCK. |
| 403 | data_residency_error | data_residency_violation | Geo-aware routing found no provider in the workspace's allowed regions. |
| 403 | ip_access_error | ip_access_denied | Client IP blocked by global or per-workspace CIDR rule. |
| 403 | workspace_credential_required | workspace_credential_required | Strict-BYOK enforcement rejected the call — the workspace has no own active provider credential. Set under dvara.credentials.require-workspace-credential=true (global) or Workspace.metadata["credentials.require-workspace-credential"] (per-workspace). Audit event: PROVIDER_CREDENTIAL_MISSING. |
| 403 | workspace_suspended | workspace_suspended | Workspace administratively suspended — trial-cliff expiry, chronic-abuse sweep, self-delete schedule, or manual operator action. Audit event per blocked request: WORKSPACE_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 workspace's tier is over its concurrency threshold. |
| 429 | provider_rate_limited | provider_rate_limited | The upstream provider credential's published rate-limit budget is exhausted; the gateway shed the request after finding no capable non-shed fallback. Carries a Retry-After header derived from the upstream signal. Requires dvara.llm-gateway.provider-rate-limit.enabled=true. |
| 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. |
Console and Portal errors
Emitted by the DVARA Flightdeck on port 8090. Same envelope shape as the data plane, same lowercased-code convention.
These codes predate the removal of the Admin API in 1.7.0 and are now raised by the Console and Portal rather than by /v1/admin/**, which returns 404. The codes and their meanings are unchanged.
Authentication and authorization
| Status | type | code | When |
|---|---|---|---|
| 401 | unauthorized_error | authentication_required | Admin caller missing or invalid Bearer token / PAT / JWT. Emitted on authentication failure. |
| 403 | forbidden_error | access_denied | Workspace-scope violation: a workspace-role caller (admin / developer / viewer) tried to read or write a resource belonging to another workspace, or a caller lacked the URL-pattern RBAC role required for the path. Emits a WORKSPACE_SCOPE_VIOLATION audit event capturing the path, method, requested workspace 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 when request validation fails. |
| 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 workspace than the caller's RBAC scope allows:
workspace_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 | Not reachable on a shipped image. The code belongs to a fallback that the real report generator always overrides, because the generator is not licence-gated — measured on 1.7.0, a SOC2 report generates on an unlicensed Development install. |
| 400 | invalid_request_error | chargeback_not_available | Not reachable on a shipped image, for the same reason as compliance_not_available above — chargeback report generation is not licence-gated either. |
| 400 | invalid_request_error | mcp_not_available | Raised by no-op MCP services where one is still reachable — for example credential rotation from the Console. It is not what an unlicensed install returns for /mcp/**: the plane is not registered at all there, so those paths answer 404 not_found. See the MCP table below. |
| 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 storage_mode is not one of ENCRYPTED / REFERENCE. |
| 400 | invalid_request_error | credential_api_key_missing | Creating or rotating a credential in the Console with storageMode=ENCRYPTED but no api_key field in the body. |
| 400 | invalid_request_error | credential_reference_missing | Creating or rotating a credential in the Console with storageMode=REFERENCE but no secret_reference 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 api_key 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 workspace. |
| 409 | duplicate_error | user_duplicate | User email already exists for the target workspace. |
| 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 Gateway errors
Emitted by the MCP plane, which runs inside the gateway on port 8080 under /mcp. Almost every error — auth, policy, agentic governance, upstream failure — comes back through the same lowercase-code, type: "mcp_error" envelope. MCP_WORKSPACE_SUSPENDED is the exception and is noted in the table below; a client that keys on type will not recognise it.
{
"error": {
"message": "…",
"type": "mcp_error",
"code": "mcp_server_not_found",
"trace_id": "…"
}
}
| Status | code | When |
|---|---|---|
| 400 | mcp_not_available | MCP Gateway requires an enterprise license. Measured on 1.7.0, an unlicensed install does not return this for a /mcp/** call — the plane is not registered, so the request answers 404 not_found with the neutral envelope. Treat 404 not_found on every /mcp path as the licence signal, and mcp_not_available as the signal from a no-op MCP service reached through some other surface. |
| 400 | mcp_pii_detected | PII detected in the tool-call arguments or response, workspace policy is BLOCK. |
| 401 | mcp_auth_required | Missing Authorization: Bearer <key> on the MCP Gateway request. |
| 401 | mcp_auth_invalid | API key does not resolve to an active workspace. |
| 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_WORKSPACE_SUSPENDED | The calling workspace is suspended. Since 1.7.0 this stops MCP tool calls and A2A hops as well as LLM requests — McpWorkspaceContextFilter and A2aAuthFilter enforce it on their own planes. This one refusal does not use the envelope above — measured on 1.7.0, it returns {"error":{"message":"Workspace is suspended","code":"MCP_WORKSPACE_SUSPENDED"}}: the code is UPPERCASE, and there is no type and no trace_id. Match on the status and the code, not on type. |
| 403 | mcp_session_killed | The session has been terminated by the Console; subsequent tool calls for that session return immediately. |
| 404 | not_found (type not_found_error) | Wrong URL under /mcp (e.g. typo, excluded actuator path, bot probe). Measured on 1.7.0 — a path no controller claims is answered with the neutral envelope, not the mcp_error one. Returned when the route or path is unknown 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 workspace. |
| 408 | mcp_approval_timeout | Approval gate reached its configured timeout with no human decision; workspace 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 Gateway 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 "Authorization: Bearer $DVARA_API_KEY" \
-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"
}
}
Workspace Not Found
{
"error": {
"message": "Workspace not found: t-999",
"type": "not_found_error",
"code": "workspace_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. Workspace 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 workspace_not_found | Workspace ID does not exist | Verify the workspace ID via the Console |
404 route_not_found | Route ID does not exist | Verify the route ID via the Console |
404 route_version_not_found | Route version does not exist | Check version history via GET the Console routes/\{id\}/versions |
404 report_not_found | Report ID does not exist | Verify the report ID via the Console |
400 pii_detected | PII found and action is BLOCK | Remove PII from request, or change workspace PII action to REDACT or LOG |
403 guardrail_blocked | Injection/jailbreak/content detected | Review prompt content; change workspace 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 workspace limits via metadata |
422 schema_validation_failed | Response doesn't match JSON schema | Review the registered schema via the Console 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 workspace metadata (guardrail.context.pruning-strategy) |
402 budget_cap_hard | Hard budget limit exceeded | Wait for next budget period or increase budget via PUT the Console 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 Gateway | Include Authorization: Bearer gw_<key> header |
401 mcp_auth_invalid | Invalid API key on MCP Gateway | Verify the API key is correct |
401 mcp_auth_revoked | API key revoked on MCP Gateway | Generate a new API key |
404 mcp_server_not_found | MCP server ID does not exist | Verify server ID via the Console |
409 mcp_server_duplicate | Server ID already exists for workspace | Use a different server_id or check existing servers |
403 mcp_policy_denied | MCP request denied by policy | Check policy rules via the Console. Review mcp_server, mcp_tool, mcp_arg conditions. |
503 mcp_server_unavailable | MCP server suspended/disabled | Re-activate the server via PUT the Console mcp/servers/\{id\}, or check upstream health via the Console 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 |