Skip to main content

Error Handling

This guide explains the platform's standardized RPC error model and how to handle errors safely in clients.

All gateway failures now use common.v1.RpcError from apis/common/v1/errors.proto.

1) Error Response Shape

For full request failures, the gateway returns a non-2xx HTTP status and a top-level RpcError JSON body.

{
"code": "ERROR_CODE_MODEL_INVALID",
"message": "all candidate models were filtered out: [invalid/model-xyz: not_in_catalog]",
"is_terminal": true,
"details": {
"error_info": {
"reason": "ALL_MODELS_FILTERED",
"domain": "openrouter",
"metadata": {
"conversation_key": "research-001"
}
},
"model_error": {
"model_id": "invalid/model-xyz",
"reason": "invalid"
}
}
}

2) Error Codes Reference

Source of truth: apis/common/v1/errors.proto. If this table and the proto ever differ, the proto wins.

ErrorCodeHTTPDefault terminal?Meaning
ERROR_CODE_UNSPECIFIED500DependsUnspecified fallback code
ERROR_CODE_CANCELLED499YesOperation cancelled by caller
ERROR_CODE_UNKNOWN500NoUnknown error
ERROR_CODE_INVALID_ARGUMENT400YesRequest payload/fields invalid
ERROR_CODE_DEADLINE_EXCEEDED504NoRequest timed out
ERROR_CODE_NOT_FOUND404YesResource does not exist
ERROR_CODE_ALREADY_EXISTS409YesResource already exists
ERROR_CODE_PERMISSION_DENIED403YesCaller lacks permission
ERROR_CODE_RESOURCE_EXHAUSTED429DependsQuota/rate limit exhausted
ERROR_CODE_FAILED_PRECONDITION400YesState not ready for operation
ERROR_CODE_ABORTED409NoConcurrency conflict, retry may succeed
ERROR_CODE_OUT_OF_RANGE400YesValue outside allowed range
ERROR_CODE_UNIMPLEMENTED501YesNot implemented
ERROR_CODE_INTERNAL500NoInternal server failure
ERROR_CODE_UNAVAILABLE503NoTemporary service unavailable
ERROR_CODE_DATA_LOSS500YesUnrecoverable data loss
ERROR_CODE_UNAUTHENTICATED401YesMissing/invalid auth
ERROR_CODE_MODEL_INVALID400YesInvalid or unsupported model ID
ERROR_CODE_MODEL_UNAVAILABLE503NoModel exists but providers unavailable
ERROR_CODE_MODERATION_FLAGGED403YesContent blocked by moderation
ERROR_CODE_GENERATION_FAILED500DependsGeneration failed after retries
ERROR_CODE_TOOL_EXECUTION_FAILED500DependsTool execution failed
ERROR_CODE_UPSTREAM_PROVIDER503NoUpstream API/provider failed
ERROR_CODE_VALIDATION_EXHAUSTED500YesStructured validation failed repeatedly
ERROR_CODE_PAYMENT_REQUIRED402YesCredits/billing required

3) Retryability (is_terminal)

  • is_terminal=true: retrying the same request is not expected to help.
  • is_terminal=false: transient condition; retry with backoff.
  • Always trust is_terminal from the payload over assumptions from HTTP status alone.
  • When present, details.retry_info.retry_delay_ms gives a minimum delay hint.

4) Error Details Fields

RpcError.details can include:

  • error_info: machine-readable classification (reason, domain, optional metadata)
  • retry_info: retry hint (retry_delay_ms)
  • field_violations: field-level validation failures
  • upstream_error: provider context (provider, status_code, raw_body)
  • model_error: model-specific context (model_id, reason, alternatives_tried)
  • help_links: URLs for recovery docs

5) HTTP Mapping

HTTP status is derived from ErrorCode by server-side mapping (pkg/restatex/errors.go):

  • 400: ERROR_CODE_INVALID_ARGUMENT, ERROR_CODE_FAILED_PRECONDITION, ERROR_CODE_OUT_OF_RANGE, ERROR_CODE_MODEL_INVALID
  • 401: ERROR_CODE_UNAUTHENTICATED
  • 402: ERROR_CODE_PAYMENT_REQUIRED
  • 403: ERROR_CODE_PERMISSION_DENIED, ERROR_CODE_MODERATION_FLAGGED
  • 404: ERROR_CODE_NOT_FOUND
  • 409: ERROR_CODE_ALREADY_EXISTS, ERROR_CODE_ABORTED
  • 429: ERROR_CODE_RESOURCE_EXHAUSTED
  • 499: ERROR_CODE_CANCELLED
  • 500: ERROR_CODE_UNKNOWN, ERROR_CODE_INTERNAL, ERROR_CODE_DATA_LOSS, ERROR_CODE_GENERATION_FAILED, ERROR_CODE_TOOL_EXECUTION_FAILED, ERROR_CODE_VALIDATION_EXHAUSTED, unspecified/unknown
  • 501: ERROR_CODE_UNIMPLEMENTED
  • 503: ERROR_CODE_UNAVAILABLE, ERROR_CODE_MODEL_UNAVAILABLE, ERROR_CODE_UPSTREAM_PROVIDER
  • 504: ERROR_CODE_DEADLINE_EXCEEDED

6) Client Handling Pattern (TypeScript)

type RpcError = {
code: string;
message: string;
is_terminal: boolean;
details?: {
error_info?: { reason?: string; domain?: string; metadata?: Record<string, string> };
retry_info?: { retry_delay_ms?: number };
};
};

async function callGateway(path: string, body: unknown) {
const res = await fetch(path, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});

if (res.ok) return res.json();

const err = (await res.json()) as RpcError;
const retryDelay = err.details?.retry_info?.retry_delay_ms ?? 1000;

if (!err.is_terminal) {
// Use your retry policy (exponential backoff, jitter, max attempts)
throw new Error(`retryable:${retryDelay}:${err.code}`);
}

// Terminal: surface a stable, code-driven UX path
throw new Error(`terminal:${err.code}:${err.message}`);
}

7) Streaming Errors

Streaming failures are emitted in LLMStreamChunkEvent when finish_reason is "error":

{
"run_id": "run_123",
"chunk_index": 42,
"is_final": true,
"finish_reason": "error",
"error": {
"code": "ERROR_CODE_UPSTREAM_PROVIDER",
"message": "OpenRouter request failed",
"is_terminal": false
}
}

For streaming consumers, treat this final chunk as terminal for that stream and branch on error.code/error.is_terminal.

8) Common Scenarios

  • Invalid model ID: ERROR_CODE_MODEL_INVALID, usually terminal.
  • Rate limited: ERROR_CODE_RESOURCE_EXHAUSTED, often retryable; check retry_info.
  • Provider outage: ERROR_CODE_UPSTREAM_PROVIDER, usually retryable.
  • Auth issues: ERROR_CODE_UNAUTHENTICATED or ERROR_CODE_PERMISSION_DENIED, terminal until credentials/permissions change.

9) Error Domain Map

details.error_info.domain identifies the subsystem that originated the error. Common values:

DomainTypical source
gatewayLLM gateway validation/request context
openrouterModel routing and provider API integration
conversationConversation actor/service operations
generationGeneration workflow orchestration
mcpMCP service/tool execution
notification / novuNotification gateway/service
storageStorage gateway/service and storage actors
apikeyAPI key service and gateway
webhook / convoyWebhook gateway/service

For full API field-level details, see LLM Gateway API Reference.