LLM request failed: provider rejected the request schema or tool payload
This is one of those errors that points at the wrong place. It reads like the model is down. It is not. The provider answered, with an HTTP 400, and refused the request your tool built. Here is what the provider is actually objecting to, the three triggers that produce it, how to fix each one, and why a wrapper that pins a single backend never enters this failure mode at all.
The error means the upstream model provider returned an HTTP 400 and rejected either your request body shape or your tool JSON schema. The model itself never ran. In a multi-provider router like OpenClaw it most often fires when a tool schema that was normalized for your primary model gets replayed unchanged against a different fallback provider during model failover. The fix is to align each provider's payload with per-provider compatibility flags (requiresStringContent, strictMessageKeys, supportsTools) under models.providers.<provider>.models[].compat, or to pin the route to a single provider so no cross-provider schema replay can happen. Authoritative source: OpenClaw troubleshooting docs.
What the words are hiding
The line bundles three separate facts and obscures all of them. Read it back to front.
The provider validated the JSON you sent against its API contract and one of two things failed: the overall message body (roles, content parts, required fields) or the tools array (each tool's name, description, and input_schema). A 400 over tools is the most common variant because tool schemas are the most provider-specific part of the payload.
Rejected, not failed. The provider returned a structured 400 invalid_request_error. This is the provider working as designed and telling you the payload is malformed. It is not a 529 overload, not a 402 billing problem, not a network timeout. Treating it like an outage and retrying will reproduce it every time.
Your wrapper caught the 400 and printed its own generic summary instead of the provider's exact complaint. The real text (for example tools.0.input_schema: unknown field or messages: text content blocks must be non-empty) is in the wrapper's debug log, not in the toast. Getting to that raw text is the whole job.
Where in the pipeline the rejection happens
The error is born at the seam between your router and the provider. Your prompt and your tool definitions are fine on their own. The router transforms them into one provider's wire format, and that transform is where the schema drifts out of spec.
The transform step is the failure point
When you have one provider, the transform is built and tested for that provider, and it tends to hold. The trouble starts the moment a second provider is reachable from the same request, because the transform that was correct for Provider A is now being asked to serve Provider B without being redone.
The three triggers, in order of how often they bite
Tool schema not re-normalized on failover
The router prepares tool definitions once, for the primary model, then fails over to a second model on a different provider and replays the same definitions. Provider B has different rules for tool input_schema (allowed JSON Schema keywords, required fields, nullability), so it answers 400. This is the signature case in OpenClaw issue #64961. Tell from this one because it works on message one and dies on a later message, right after a retry.
Message format the backend will not accept
Some backends reject a messages array that is system-only with no trailing user turn (reported for ZAI / GLM in OpenClaw issue #68735). Others reject structured content parts and want a plain string, or reject extra OpenAI-style replay metadata on the message keys. Each is a 400 over the body rather than the tools.
Missing state a provider requires passed back
A few providers require fields from a prior tool-call turn to be echoed in the follow-up (for example a model that needs its reasoning_content preserved across a tool round trip). Drop that field and the next request is structurally invalid, so the provider rejects it even though your first turn worked. Reported across Azure Foundry GPT deployments in OpenClaw issue #65603.
Fixing it in a router, step by step
The fix is always to get the raw 400 text, match it to a cause, and apply the narrow compat flag for that provider. Do not guess and do not start with disabling tools globally.
Get the raw provider error
Run openclaw logs --follow and reproduce. The provider's exact 400 string is in the log even when the UI only shows the generic line. That string names the field that is wrong.
Decide: body or tools
If the text mentions tools, input_schema, or a JSON Schema keyword, it is a tool-schema rejection. If it mentions content, type sequence, message keys, or a missing user turn, it is a body rejection. They have different fixes.
Apply the narrow compat flag
Under models.providers.<provider>.models[].compat: set requiresStringContent: true for 'invalid type: sequence, expected a string'; strictMessageKeys: true for errors referencing validation.keys; supportsTools: false only as a last resort to drop tools for a backend that cannot handle them.
models:
providers:
<provider>:
models:
- id: <model-id>
compat:
requiresStringContent: true # body wants plain string content
strictMessageKeys: true # drop extra OpenAI replay metadata
# supportsTools: false # last resort for a no-tools backendConfirm with a tools-off bisection
If you still cannot localize it, turn tools off once. Failures gone means the tool schema was the cause. Failures shrink but remain means there is a second body-format or capacity issue underneath. Re-enable tools after you know.
Why a single-backend wrapper never enters this failure mode
Every cause above shares one precondition: a single request can reach more than one provider, so a schema built for one is replayed against another. Remove that precondition and the whole class disappears. That is the design choice behind Fazm, the native macOS app that wraps Claude Code and Codex over ACP.
Fazm pins every chat to exactly one ACP backend. A chat is Claude, or Codex, or Gemini, for its whole lifetime. There is no silent mid-request failover to a second provider, so a tool schema is never normalized for one model and then quietly replayed against another. Switching backend is an explicit per-chat action, and when you do it a cross-provider switch guard tears down the previous provider's session before the target backend starts. In the bridge source the guard logs a [PROVIDER-SWITCH] line and drops the foreign session for that key; you can read it in acp-bridge/src/index.ts in the open-source repo.
When a provider does answer with a real 400 invalid_request_error, Fazm does not swallow it. A small classifier, classifyApiFailure(errMsg, apiRetryInfo) in acp-bridge/src/api-failure.ts, sorts upstream failures into three buckets: overloaded (a 529), credit (402, 429, or a billing message), and other. A 400 schema or tool rejection lands in other, and the caller surfaces the raw provider message verbatim instead of relabeling it. There is a test case asserting exactly that in acp-bridge/test/classify-api-failure.test.mjs: the input "400 invalid_request_error — messages: text content blocks must be non-empty" must classify as other. The credit regexes are deliberately bounded ([^.]{0,N} rather than a greedy .*) so an unrelated 400 whose text merely contains the word "limit" is not hijacked into the credit bucket and made to look like a billing problem. That discipline exists because the opposite bug, misclassifying a transient 529 as credit exhaustion, took down in-flight sessions in production on 2026-05-14.
| Feature | Multi-provider router | Fazm (single pinned backend) |
|---|---|---|
| Mid-request failover across providers | Yes, a single request can retry against a second provider | No, each chat is pinned to one ACP backend for its lifetime |
| Tool schema replayed against a foreign provider | Possible: schema normalized for the primary model is reused on fallback | Never: no cross-provider replay path exists in a chat |
| Switching backend | Automatic and silent on retry | Explicit per chat, guarded by a provider-switch teardown |
| A real provider 400 | Sometimes flattened into a generic 'request failed' line | Surfaced verbatim via classifyApiFailure's 'other' bucket |
A multi-provider router buys you automatic fallback and broad model choice. The tradeoff is the schema-replay surface this error lives on. Pinning a backend removes the surface; it does not give you free failover.
This is not an argument that routers are wrong. If you want one request to fall back across many providers, you accept that the transform has to be redone per provider per attempt, and you fix the cases where it is not. It is an argument that if you keep seeing this error and you do not actually need cross-provider failover, the cheapest fix is to stop letting one request touch two providers.
Tired of debugging your agent's request builder?
Walk through your setup with us and see whether pinning one local backend removes a whole class of these errors for your workflow.
Questions people actually ask about this error
What does "provider rejected the request schema or tool payload" actually mean?
It is a wrapper message for an upstream HTTP 400. Your agent tool or router built a request, sent it to a model provider's API (Anthropic, OpenAI, Azure OpenAI, an OpenAI-compatible gateway, and so on), and the provider returned a 400 saying the request body or the tool definitions do not match the shape it accepts. The wrapper does not always print the raw provider text, so you see the generic 'schema or tool payload' line instead of the specific field the provider complained about. The fix always lives in the request the wrapper built, not in your prompt.
Why does it only happen after a model switch or fallback?
Because tool-schema normalization usually happens once, for the primary model, and is not repeated per attempt. When the router fails over to a second model that belongs to a different provider with different schema rules, it replays the primary model's tool definitions unchanged, and the fallback provider rejects them with a 400. This exact failure mode is tracked in OpenClaw issue #64961 ('Tool schema not re-normalized during model failover'). If your request works on the first message and dies on a later one, suspect failover.
How do I fix it in OpenClaw specifically?
OpenClaw exposes per-provider compatibility flags under models.providers.<provider>.models[].compat. The troubleshooting doc names three: compat.requiresStringContent: true when the backend rejects structured content parts ('invalid type: sequence, expected a string'), compat.strictMessageKeys: true when it rejects extra message metadata (errors referencing validation.keys or allowed keys like role and content), and compat.supportsTools: false to drop tool schemas entirely for a backend that cannot handle them. Run openclaw logs --follow, read the raw 400 text, and match it to one of those flags.
Is this an Anthropic or OpenAI problem I should report to them?
No. A 400 invalid_request_error is the provider working correctly: it is telling you the payload is malformed for its API. The bug is in whatever assembled the request. Reporting it to the model vendor will not help. The thing to debug is your gateway or agent framework's request builder, its tool-schema normalizer, and its message formatting (system-only messages, replay metadata, content-part shape).
Does Fazm produce this error?
Fazm pins every chat to one ACP backend (Claude Code, Codex, or Gemini) and never silently fails a single request over to a different provider mid-flight. Switching backend is an explicit per-chat action, and a cross-provider switch guard tears down the previous provider's session before the new backend starts, so a tool schema built for one provider is never replayed against another. When a provider does return a real 400, Fazm's classifier surfaces the raw provider message verbatim instead of swallowing it, so you debug the actual complaint, not a generic line.
Can disabling tools confirm whether tool schema is the cause?
Yes, as a diagnostic. If you turn tools off (in OpenClaw, compat.supportsTools: false) and the failures stop, the tool schema surface was the thing the provider rejected. If failures shrink but do not disappear, tool schemas were part of the pressure but there is a second issue, usually message formatting or upstream capacity. It is a bisection step, not a permanent fix, because most agent workflows need tools.
Why does the same config work for one message and then break?
Two common reasons. First, failover: the first message hits your primary provider, a later one trips a retry to a fallback provider that rejects the replayed schema. Second, conversation state: some providers reject a follow-up turn that is missing fields they require to be passed back (for example reasoning_content on certain models), or a messages array that has become system-only after pruning. The request that fails is structurally different from the one that succeeded, even though your settings did not change.
Related reading
Claude Code 400 bad request through a proxy or custom base URL
The other common 400: a proxy or custom ANTHROPIC_BASE_URL mangling the request before it reaches Anthropic.
Custom API base URL and corporate proxy routing
How to route an agent through a corporate proxy or Anthropic-compatible gateway without breaking the payload.
Control Claude Code context compaction
Keeping full chat history live instead of letting a router silently rewrite or compact it.
Comments (••)
Leave a comment to see what others are saying.Public and anonymous. No signup.