output_schema_invalid 400: read violations path and rule tokens
A Sume Format run with a schema outside the supported subset returns 400 output_schema_invalid with every violation listed. Switch on rule, never on message.

When the output_schema on a Sume Format run is outside the supported subset, the create call returns 400 output_schema_invalid and nothing runs, so nothing is charged. The body carries details.violations[], each entry { path, rule, message }, and every problem is reported at once, not just the first.
Fix a schema from one response by branching on rule, the stable lowercase token, and using path to find the node. message is written for a human and may change, so never match on it.
What is in a violation?
path is a JSON-Pointer-style location such as #/properties/scenes/items/properties/clip. rule is the token documented on the structured output page, safe to switch on. message explains the problem in words and is for logs.
Because all violations arrive together, a schema ported from another provider usually needs one round trip, not ten. The most common batch is a trio: an object missing additionalProperties: false, a property missing from required, and an oneOf that should be anyOf.
| rule | Meaning | Usual fix |
|---|---|---|
| root_must_be_object | Root is not exactly type object | Wrap arrays or unions in an object property |
| missing_type | Node has no type, $ref or anyOf | Add a type |
| unsupported_keyword | Keyword is off the allowlist | Replace oneOf with anyOf, flatten allOf |
| additional_properties_false | Object lacks additionalProperties false | Add it on every object, including $defs |
| required_completeness | Declared property not in required, or the reverse | List every property in required; use a nullable union |
| missing_items | Array has no items | Declare items |
| unsupported_ref | $ref is not #/$defs/name or SumeMediaFile# | Move definitions to root $defs |
| invalid_defs | $defs is not an object of schemas | Fix the $defs block |
How do I turn violations into fixes in code?
The sample maps each rule token to a one-line hint and prints the path next to it. It runs as written against a sample error body, and it falls through on a token it does not know, which matters because the documented size limits and other rules can grow.
Keep your own hint table small and let unknown tokens print the server's message, so a new rule still reaches you.
import json
HINTS = {
"root_must_be_object": "wrap it in an object property",
"unsupported_keyword": "use anyOf instead of oneOf; flatten allOf",
"additional_properties_false": "add additionalProperties: false",
"required_completeness": "list every property in required",
"missing_items": "declare items on the array",
}
err = json.loads('''{"error": {"code": "output_schema_invalid", "details": {"violations": [
{"path": "#", "rule": "additional_properties_false", "message": "open object"},
{"path": "#/properties/a", "rule": "unsupported_keyword", "message": "oneOf"},
{"path": "#/properties/b", "rule": "new_rule", "message": "see docs"}]}}}''')
for v in err["error"]["details"]["violations"]:
print(v["path"], "->", HINTS.get(v["rule"], v["message"]))Does strict false skip any of these checks?
No. strict: false is accepted and stored, and it changes nothing: a schema outside the subset is rejected whether strict is true or false. There is also no JSON mode equivalent, so the choices are a bound schema or the built-in schema.
If you are porting a schema from another provider, read the OpenAI response_format post and the optional field post, which cover the two ports that fail most often.
What if the schema is valid and the run still fails?
Then you are no longer at submit. A schema that passes validation can still produce output_schema_unsatisfied after the run, with violations[] or rejected_urls[] in output_error.details. That is a failure of the finished run to fit your schema, and it is read from the receipt, not from a 400. A 400 here costs nothing; a post-run failure may already have billed generation.
Sources
Related posts
More in Formats
- Sume output_schema limits: depth 10, 5000 properties, 120,000 chars
Sume's output_schema caps nesting at 10 levels, 5000 properties, 1000 enum values and 120,000 characters of strings. See which rule fires and how to shrink.
- Pick a Format from the list: io profile and showcase before you run
GET /v1/formats returns each Format with an io profile (input_kind and output_kind) and a showcase output, so you can choose one without paying for a trial run.
- previous_run_not_resumable: a Format run with nothing to continue
Continuing a Format run with previous_run_id returns 400 previous_run_not_resumable when the run left no thread or artifacts. The four refusals and what to do.
- primary_output_key: how a Format run picks primary_output_url
A Format run resolves primary_output_url from three places in order. Why a text key gives a null URL, and when a failed run still returns no pointer.
Written by Sume