ErrLookup › Background articles › Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema
Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema
Schema validation failed errors — invalid input schema, document structure is invalid, does not match expected format — appear when a library accepts your data as syntax (usually valid JSON) but rejects its structure: missing required fields, wrong types, unknown keys, out-of-enum values, or contradictory cross-field values. Developers meet this family when registering MCP tools, configuring embedding clients, importing export files, replying to agent events, or calling APIs whose response shape is checked. The fix is almost always to match the documented shape exactly, guided by the field path, key list, or wrapped cause embedded in the message.
Distilled from 187 documented records across 28 repositories.
Background
These errors come from a validation layer that sits between decoding and use. Once JSON parses or a struct deserializes, a guard checks the payload's structure before it is trusted: a JSON Schema compiled with Ajv (Chroma's JS clients) or jsonschema-go (SiYuan's MCP tool registration), a Standard Schema instance validated through Zod/Valibot safeParse (Airi's stage view-state parsers, Caveman's tool inputs), a hand-written type guard (Ruflo's RVFA header, Ruflo's event log, Caveman's integration journals), a whitelist of allowed keys (Rocket.Chat room settings), or construction against a TypedDict (LiteLLM's Nova Canvas params). The guards exist to fail fast: bad input stops before it reaches a tool implementation, a full-text search index, a host tool registry, or rollback logic that cannot recover from malformed state.
From the caller's side the rejection appears at a boundary: a registerTool call, a config set on a collection, a file import, a reply to a pending event, or an HTTP 400 from a server endpoint. Message quality varies widely across the family. Some errors are precise — Chroma concatenates '<instancePath>: <ajv message>' for every violation, the remotive provider embeds the top-level keys it received ('got keys: [message, code]'), and LiteLLM names the field, the expected type, and the actual type. Others wrap their cause (SiYuan's 'invalid input schema: %w', LiteLLM's appended '{e}') or return a rule name in the 400 body (impeccable's appliedEntryIds_must_contain_strings). In every case the payload was syntactically fine; the objection is structural, which separates this family from parse errors such as 'Unexpected token'.
The family varies along two axes: what enforces the schema, and where the schema lives. Enforcement ranges from declarative JSON Schema documents to imperative cross-field checks (Ruflo rejects budgetUsdPerRun greater than budgetUsdMonthly), status-consistency rules (impeccable rejects status 'error' alongside applied entries), and invariant pairings (RustFS requires strict-ingress serde policies to deny unknown fields and compat policies not to). The schema itself may live in code, in version-controlled JSON files that gain required fields on upgrade (Chroma's provider schemas), or only implicitly in whatever a writer emitted — exports, journals, and state files. The implicit case produces a version-drift variant: a file written by an older or newer version (an Airi chat export with a different format marker, ECC's legacy-sync-state schema value, a Caveman journal missing owned blocks, a legacy Chroma collection without vector-index defaults) parses cleanly but fails the current reader's shape check, and the remedy is regeneration with a matched version rather than field-by-field patching.
Common causes
- Wrong top-level type. A string, array, number, or null where the schema requires a plain object. Classic forms: a JSON.stringify'd schema or header config passed unparsed, a curl-style 'Content-Type: application/json' string, or a JSON-Schema type array like ["string", "null"] at the top level.
- Missing required field. A key the schema demands is absent or empty: no inputSchema on a registered tool, no model_name in an embedding config, no searchable text field on a memory record, or an event without aggregateId.
- Wrong key name or casing. The field exists but under the wrong name: roomtopic instead of roomTopic, camelCase config keys where the provider schema wants snake_case, negative_text instead of negativeText.
- Field value has the wrong type. A string where a number is declared (order: "1", count as "5"), a number where a string is declared (aggregateId: 42), or a scalar where the schema expects a list or dict.
- Value outside the allowed enum. The field is present and typed but not in the picklist: a Godot reason or error code missing from the schema's enum, boot.isolation 'docker' instead of container|microvm|native, or a format marker other than 'chat-sessions-index:v1'.
- Unknown or extra fields. The payload carries keys the target does not know: a foreign setting key in saveRoomSettings, an extra kwarg hitting a TypedDict, an attribute not in the collection definition, or any extra key under a strictObject.
- Schema drift between writer and reader. The payload was written by a different version or a redesigned upstream: old app exports, journals from an older tool version, collections predating vector-index defaults, or an API that renamed its response key while still returning HTTP 200.
- Cross-field or semantic contradiction. Each field is individually valid but the combination fails: budgetUsdPerRun exceeding budgetUsdMonthly, status 'error' while appliedEntryIds lists applied work, or peer entries with machine keys that do not match the node identity.
What usually fixes it
- Mine the message before changing anything. Most errors in this family embed the exact violation: JSON-pointer instance paths, the top-level keys actually received, field name plus expected and actual types, or a wrapped cause (%w, {e}). Unwrap and read it; the fix differs per underlying cause.
- Conform the payload to the documented shape exactly. Copy the documented template with exact casing and only allowed keys, put values in the declared types, keep enum values inside the allowed sets, and open the schema file or whitelist in the source when unsure which names are accepted.
- Parse serialized data at the boundary. Never pass a JSON string where an object is expected; JSON.parse schema text, header configs, and forwarded payloads before validation sees them, and build headers as object literals rather than strings or tuple arrays.
- Regenerate instead of hand-editing. For files a tool wrote (exports, journals, state files, image headers), re-run the current version's writer — re-export, re-enable, createDefaultHeader — rather than patching fields; hand edits are a documented trigger across the family.
- Keep both sides of the contract in lockstep. Extend picklists in the same change that adds a new code or reason, pin producer and consumer versions together (client and server, engine and host), upgrade together, and diff schema files after upgrades since they gain required fields.
- Validate before the library does. Compile schemas in CI with the same validator, type payloads so unknown keys fail at compile time, replicate searchable-content and shape checks client-side as pre-flight checks, and test every emitted code string against the schema so drift fails loudly and early.
Go deeper
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Documented occurrences
- Tool input schema must be a JSON Schema object or a Standard Schema instance. (moeru-ai/airi)
- RVFA header failed validation (ruvnet/ruflo)
- invalid_manual_apply_result: invalid_manual_apply_result (pbakaus/impeccable)
- Invalid model_max_budget: {e}. Example of valid model_max_budget: https://docs.litellm.ai/docs/proxy/users (BerriAI/litellm)
- Config validation failed for schema '${schemaName}': ${errorPaths} (chroma-core/chroma)
- invalid json schema: %v (siyuan-note/siyuan)
- error-invalid-settings: Invalid settings provided (RocketChat/Rocket.Chat)
- Schema is missing defaults.float_list.vector_index (chroma-core/chroma)
- Invalid chat session export format (moeru-ai/airi)
- INVALID_HEADERS: headers must be an object (ruvnet/ruflo)
- Invalid type for field '{field_name}': expected {getattr(expected_type, '__name__', str(expected_type))}, got {type(value).__name__} (BerriAI/litellm)
- Hermes integration journal lacks owned routing block (JuliusBrussee/caveman)
- Invalid Godot stage view-state snapshot payload. (moeru-ai/airi)
- Invalid Godot stage view-state error payload. (moeru-ai/airi)
- cave_tool_input_schema_mismatch: cave_tool_input_schema_mismatch:${options.name} (JuliusBrussee/caveman)
- Codex integration journal lacks owned blocks (JuliusBrussee/caveman)
- Event must have a valid aggregateId string (ruvnet/ruflo)
- #${e.num}: Score has markdown bold: "${e.score}" (santifer/career-ops)
- document_invalid_structure: The document structure is invalid. Please ensure the attributes match the collection definition. (appwrite/appwrite)
- pod-template at /: budgetUsdPerRun must not exceed budgetUsdMonthly (ruvnet/ruflo)
…and 167 more across the corpus — use search.
Honest provenance: generated on 2026-08-20 from AI-assisted analysis of the linked records. See how records are made.