LatentKit

Error Reference

Stable error codes, categories, retry guidance, and request ID debugging.

Non-success responses use a structured envelope:

{
  "error": {
    "code": "UPSTREAM_ERROR",
    "category": "upstream",
    "message": "Underlying provider returned an error.",
    "provider": "anthropic",
    "model": "claude-sonnet-4-6",
    "request_id": "req_abc123",
    "retryable": true,
    "suggested_action": "retry_with_backoff"
  }
}

Top-level code, message, and request_id are also present for older SDKs. New integrations should read error.code and error.category.

Categories

CategoryMeaningGeneric handling
authLatentKit API key or upstream credential problemFix credentials; do not retry blindly.
billingCredits or billing state blocks executionSurface to workspace owner; retry after top-up or plan change.
policyRouting policy, guardrail, or request eligibility refused executionChange policy or request; retries usually repeat the same failure.
quotaLatentKit or upstream rate limitRetry idempotent requests with backoff.
upstreamProvider timeout, 5xx, invalid JSON, or provider-side failureRetry with exponential backoff; inspect provider/model.
routingNo healthy eligible route could executeCheck policy, connections, provider health, and model capability.
gatewayLatentKit deadline or internal failureRetry transient failures; include request ID with support tickets.
model_outputProvider returned a syntactically successful but unusable model resultRetry with stricter tool choice, larger token budget, or deterministic fallback.

Codes

HTTPerror.codeerror.categoryRetry?Meaning
200 / 503TOOL_CALL_NOT_PRODUCEDmodel_outputYesA tool call was required but the model returned text.
200 / 503TRUNCATED_OUTPUTmodel_outputYesOutput ended because the token budget was too small.
401INVALID_API_KEYauthNoMissing, invalid, expired, or revoked LatentKit API key.
402INSUFFICIENT_CREDITSbillingNoWorkspace lacks Platform Access credits or budget.
403POLICY_REJECTEDpolicyNoGuardrail, route policy, or eligibility rejected the request.
408UPSTREAM_TIMEOUTupstreamYesProvider timed out before returning a response.
409IDEMPOTENCY_KEY_REUSEDgatewayNoSame Idempotency-Key was replayed with a different payload.
409IDEMPOTENCY_REPLAY_IN_PROGRESSgatewayYesThe original request for this key is still running; retry shortly.
409IDEMPOTENT_RESULT_UNAVAILABLEgatewayNoThe stored result was too large to replay; use a new key.
413REQUEST_TOO_LARGEgatewayNoRequest body exceeds the edge/app body limit.
413AUDIO_TOO_LARGEgatewayNoAudio input exceeds the audio byte budget.
429RATE_LIMITEDquotaYesLatentKit or upstream rate limit was hit. Retry-After is always present.
502UPSTREAM_ERRORupstreamYesProvider returned a failed response or invalid payload.
503NO_HEALTHY_PROVIDERroutingDependsCheck retryable: false + suggested_action: "attach_provider_for_capability" means a configuration gap (attach a provider — retries cannot help); true means transient provider unhealth.
504EXECUTION_TIMEOUTgatewayYesLatentKit's end-to-end execution deadline expired.

Oversized uploads and queue payloads are rejected with the structured envelope (413 for streamed uploads); reduce the payload instead of retrying — see Rate limits.

Backoff guidance

For quota, upstream, routing, and gateway retryable errors, use jittered exponential backoff and cap retries. For model_output, a retry can help, but repeated failures usually mean the prompt, tool_choice, or max_tokens needs to change.

What LatentKit retries for you

Inside a single request, LatentKit only auto-retries transport failures that provably happen before the request reaches the provider (connection establishment). Ambiguous failures — read timeouts and protocol errors after the provider may have accepted the request — are never replayed automatically, because the provider might already have run and billed the generation. They surface as UPSTREAM_TIMEOUT or UPSTREAM_ERROR so your application decides whether a replay is safe.

Route-level failover is separate: when an attempt fails with a retryable provider error, the published policy's next eligible provider/model entry is tried, and you still receive one response.

An unknown purpose is not an error

A purpose that is not configured, is disabled, or cannot serve the endpoint's capability does not produce an error. The request runs on the app's assigned route and returns 200. This is deliberate: shipping application code that names a purpose before someone configures it in the console must not take the feature down.

Detect it on the response rather than in an error handler:

SignalMeaning
lk_applied_purpose is null while you sent a purposeThe request ran on the app default
lk_purpose_source is purpose_unknownThe route does not declare that label (purpose_disabled means it exists but is turned off)
lk_purpose_source is purpose_no_capable_routeThe bound route cannot serve this endpoint

A malformed purpose is a different case and does fail loudly:

HTTPCondition
400The label does not match ^[a-z][a-z0-9_-]{0,39}$, or uses the reserved lk- / lk_ prefix — sent in the body or as X-LK-Purpose

So a typo in your code is a hard error, while a missing console binding is a soft fallback. Log lk_applied_purpose in production; the console also charts degradations per purpose on the app's analytics.

Debugging with request IDs

Every response should include X-LK-Request-ID; JSON bodies also include error.request_id. Include that ID when contacting support or correlating your app logs with LatentKit request logs.

On this page