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.

5 min readSume
All posts

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.

anyOf placement rules, read 2026-10-02
RuleWhat it means in practice
Only anyOf is allowedReplace oneOf; flatten allOf
Root must be type objectPut the anyOf on a property, not at the top
anyOf short-circuits its nodeSibling keywords beside it carry no meaning
Each branch is a full nodeIt 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

All Formats posts

Written by Sume