DodoRouter
Get Started
Docs / Troubleshooting & FAQ
Browse docs
View as Markdown

Troubleshooting & FAQ

Codex shows zero cache reads while Dodo's logs show hits

Earlier Responses egress returned only input/output/total tokens, dropping input_tokens_details.cached_tokens. It also omitted output reasoning details. The formatter now restores both detail objects. Update DodoRouter if the final response.completed usage omits details present in the provider's usage.

Codex resume: content[0].text received an array

Earlier Responses conversion wrapped structured assistant output blocks inside output_text.text, which must be a string. Assistant content arrays now remain arrays of blocks. This fixes a case where a single-turn smoke test passed but the next turn of the same session failed.

Codex Responses-Lite requires reasoning.context to be all_turns

Earlier Responses ingress kept only the client's reasoning effort, losing context while forwarding the Responses-Lite header. The full client reasoning object now survives conversion. Update DodoRouter if this field disappears between the incoming and outbound requests; do not strip the header or hardcode context as a workaround. Provider-default routing does not erase client settings.

Codex: input[0].content is null

Earlier DodoRouter versions treated Codex's additional_tools input item as a normal developer message, replacing its type and tools with content: null. The Responses conversion now preserves non-message typed input items. If the outbound request shows this corruption, update DodoRouter; do not work around it by deleting the client's tools or inserting empty text.

Inspect each attempt in the request's Trace: the last fallback's error can be different from the initial provider's rejection. A final developer-role error does not explain an earlier Responses invalid_type error. A fallback to a different API format may still be incompatible with native Responses items.

"No routing configured for router '…'"

The router has zero routing steps. Add at least one on the router's page under Routing Chain.

"Failed to store API key securely" when adding a provider key

Self-hosted only: INFISICAL_TOKEN / INFISICAL_PROJECT_ID aren't set or are invalid. See the callout in Self-hosting.

401 Invalid API key

You're using a provider key (from the Providers page) where a router API key is expected, or vice versa — see Provider keys vs. router API keys. Also check that the key belongs to the router in the URL and hasn't been revoked. Create a new key on the API Keys page if you no longer have the secret; other active keys keep working.

401 on /mcp, even with a valid router key

The MCP endpoint doesn't take a router API key at all — that key sends traffic, and reading traffic back is a separate grant. Connect the agent over OAuth instead: claude mcp add --transport http dodorouter {base_url}/mcp, then approve it in the browser. See Agent access.

402 Payment Required

Hosted service only: the account owning this router doesn't have an active subscription. Visit Billing to subscribe. Doesn't apply to self-hosted instances unless you've configured Stripe billing yourself.

400 "Input exceeds context window of this model"

Every routing step failed with a context-overflow error — either you only have one step and its model's context window is too small, or you've enabled "Skip fallback on context overflow" and the first step alone couldn't fit the request. Add a step with a larger context window, or disable that toggle so DodoRouter tries the rest of the chain.

502 "All providers failed"

Every step in the chain returned a non-recoverable error. Open the request in Logs → it shows the per-step error for each attempt, which is almost always more specific than the top-level message.

Internal proxy exception in a fallback step

An Internal proxy exception means DodoRouter crashed while executing that step; it is not an error response from the provider. The Trace keeps earlier attempts and their errors, then identifies the provider and model of the step that crashed. For example, an OpenAI authentication failure followed by an Anthropic conversion exception appears as two separate attempts.

If no outbound body was recorded, the log cannot establish whether that step sent a request. It does not claim the request passed through unchanged.

Older crash logs may show only one attempt labeled with the first provider, even when a later step crashed. Those records lost the earlier attempt history; the fix cannot reconstruct it. Use server logs and the request ID to recover the original sequence when available.

A routing step shows "invalid" or "out of credits" next to its assigned key

DodoRouter tracks key health from real dispatch outcomes. Fix or replace the key on the Providers page; the warning links straight there.

Do I need to send a real model name?

No — see The model field is ignored. DodoRouter always uses the model configured on the routing step it's attempting.

Why did my prompt cache stop hitting?

Open the diverging request's Cache evidence or read get_log.cache_diagnosis through MCP. prefix_changed names the first changed component and one-based item index (tools, system prompt, or conversation message); breakpoint changes are reported separately. The signatures contain no prompt text or tool names and survive body truncation/removal. They identify a structural change, not a proven provider cache invalidation.

With an unchanged shared prefix, cache_expired means the gap exceeded a uniform, explicitly requested TTL (likely); parallel_race means the compared request overlapped this one (possible). provider_no_hit means zero reported cache-read tokens with no structural difference found, not a confirmed provider defect. unknown covers missing baselines, routing changes, and parameter changes whose cache effects are unknown. Missing cache usage is unreported, never an assumed zero.

The comparison sees only completed logs available when this row was written. It cannot see provider shards, exact expiry, all concurrent requests, or external traffic; it does not invent a different_shard diagnosis. Mixed or omitted TTLs do not establish an expiry. Concurrent or forked traffic sharing a session may compare unrelated turns; diagnostics use only the session ID the client already supplies. Sessionless global prefix lookup and exact token-offset localization are not included.