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.

M
Matthew Diakonov
8 min read
Direct answer · verified 2026-06-21

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 request schema or tool payload"

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.

"provider rejected"

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.

"LLM request failed"

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

Your prompt
Your tools
Session state
Router transform
Provider A
Provider B (fallback)

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

01

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.

02

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.

03

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.

1

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.

2

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.

3

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 backend
4

Confirm 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.

The part you can verify in the source

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.

FeatureMulti-provider routerFazm (single pinned backend)
Mid-request failover across providersYes, a single request can retry against a second providerNo, each chat is pinned to one ACP backend for its lifetime
Tool schema replayed against a foreign providerPossible: schema normalized for the primary model is reused on fallbackNever: no cross-provider replay path exists in a chat
Switching backendAutomatic and silent on retryExplicit per chat, guarded by a provider-switch teardown
A real provider 400Sometimes flattened into a generic 'request failed' lineSurfaced 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.

How did this page land for you?

React to reveal totals

Comments ()

Leave a comment to see what others are saying.

Public and anonymous. No signup.