Sume output_schema anyOf: two result shapes without oneOf
Model a Format result that is either a video or an image set with anyOf in a Sume output_schema. Wrap it below the root and put constraints in each branch.

To describe a Format result that can take one of two shapes, use anyOf in your Sume output_schema, not oneOf. Put the anyOf on a property below the root, because the root must be exactly type: object, and put every constraint inside the branches, because anyOf short-circuits the node it sits on.
Those three rules, plus the strict-subset requirements that every object has additionalProperties: false and lists all its properties in required, are enough to model a result such as a video or an image set. This post builds that schema step by step.
Why anyOf and not oneOf?
The structured output page is explicit: only anyOf is on the allowlist, and oneOf is rejected as unsupported_keyword. Schemas ported from OpenAPI reach for oneOf by reflex, so it is the first thing to change.
The two are not identical, because anyOf permits a value that matches several branches. Make the branches disjoint with a const discriminator, so exactly one can match in practice.
| Rule | What it means in practice |
|---|---|
| Only anyOf is allowed | Replace oneOf; flatten allOf |
| Root must be type object | Put the anyOf on a property, not at the top |
| anyOf short-circuits its node | Sibling keywords beside it carry no meaning |
| Each branch is a full node | It needs a type, additionalProperties false and full required |
What does the schema look like?
The result is wrapped in an object with a single result property. Each branch carries a kind field fixed with const, so your code can switch on it, and each branch lists every property in required. Media fields use SumeMediaFile#, which makes the URL gate check that every URL was really produced by the run.
Notice there is no minItems on the image array. If you want a partial result to stay legal, leave it off; minItems is genuinely enforced.
{
"name": "acme/hero-or-set/v1",
"strict": true,
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["result"],
"properties": {
"result": {
"anyOf": [
{ "type": "object", "additionalProperties": false,
"required": ["kind", "video"],
"properties": { "kind": { "type": "string", "const": "video" },
"video": { "$ref": "SumeMediaFile#" } } },
{ "type": "object", "additionalProperties": false,
"required": ["kind", "images"],
"properties": { "kind": { "type": "string", "const": "images" },
"images": { "type": "array", "items": { "$ref": "SumeMediaFile#" } } } }
]
}
}
}
}What about primary_output_key?
primary_output_key resolves a top-level key of output. With the wrapped shape, the top-level key is result, which holds an object, not a media file, so the receipt's primary_output_url would not resolve to the video. If you need a single URL on the receipt, give the schema a top-level media field and use anyOf only for the extras.
A named key that carries a non-media value is echoed back with primary_output_url: null, as the docs state, so this is a silent mismatch worth testing once.
What can still go wrong?
A branch that demands media the Format never makes will fail the same way a flat schema does: output_schema_unsatisfied, with a harvested count by media type in the details. The anyOf helps here, because the second branch gives the run a legal shape when the first cannot be filled. See the optional field post for the nullable-union alternative, which is simpler when the shapes differ by one field.
Sources
Related posts
More in Formats
- output_schema_unsatisfied with rejected_urls: the Sume URL gate
A Format run that returns output_schema_unsatisfied and rejected_urls named a media URL it did not generate. How the URL gate works and how to fix the schema.
- Format package rules: skill_path_invalid, file names, and limits
What a Format package may contain over the Contents API: SKILL.md name equals slug, two directories, a file-name pattern, five extensions, and 1000 paths.
- Format reads inactive but still runs: the 409 codes
A never-run Format may read inactive until its first API run. The real refusal is a 409 format_inactive or format_api_trigger_disabled.
- Format run 401 with a valid key: Bearer and x-api-key both sent
A Sume Format run returns 401 unauthorized when a request carries two credentials at once. Send Authorization Bearer or x-api-key, never both.
Written by Sume