OpenAI response_format json_schema to a Sume output_schema

Moving a json_schema from OpenAI structured outputs to a Sume Format run: the field name, what transfers, no JSON mode, and why output comes once, post-run.

5 min readSume
All posts

Send your schema to a Sume Format run as output_schema, or paste the OpenAI-shaped response_format object unchanged. Most of the strict-mode rules you already follow carry over. The difference is where the schema applies: OpenAI constrains what the model says, while Sume constrains how a finished run is read back.

Sume's structured output docs have a table for this move. This post walks through it.

What does the request look like?

response_format is accepted as an alias for output_schema, in the Chat Completions spelling only. Sending both fields is 400 invalid_request, and the text.format spelling from the Responses API is not accepted. The name is required in both places; Sume says to namespace it, because it lands on every receipt.

{
  "instruction": "Write the headline and render a hero image.",
  "input": { "product_url": "https://shop.example.com/p/8823" },
  "output_schema": {
    "name": "acme/hero/v1",
    "strict": true,
    "schema": {
      "type": "object",
      "additionalProperties": false,
      "required": ["headline", "hero_image"],
      "properties": {
        "headline": { "type": "string" },
        "hero_image": { "$ref": "SumeMediaFile#" }
      }
    }
  }
}

Which rules are the same?

The OpenAI guide requires the root to be an object, additionalProperties: false, and every field declared required, with optional fields written as a union with null. Sume enforces the same ones, and goes further with an allowlist: a keyword not on it is a violation rather than silently ignored. oneOf is rejected, so use anyOf; allOf is rejected, so flatten the branches. See how to make a field optional.

What is different?

Check this table before porting code.

OpenAI structured outputs against Sume output_schema, from the Sume docs and the OpenAI guide (read 2026-10-02).
TopicOpenAISume Format run
Loose JSONJSON mode exists beside schema modeNo equivalent of json_object; bind a schema or take the built-in one
strictEnforces schema adherenceAccepted and defaults true, but changes nothing: the subset is always enforced
Who emits the JSONThe modelA post-run projection
RefusalsA refusal fieldoutput_error on the receipt, a failed projection rather than a safety refusal
StreamingPartial JSON can streamoutput appears once, on the terminal receipt
Root recursionPermitted via $ref: "#"Rejected; recurse through a named #/$defs/* entry
MediaNot applicableSumeMediaFile# plus the URL gate

What about the built-in shape?

Bind nothing and output is projected onto sume/action-run-output/v1: a nullable text and four arrays, images, videos, audio and files. It is filled deterministically from the run's media and final text, with no model involved, so it cannot fail the way a custom schema can. If you want the media without designing a schema, that is enough to ship on.

What should I change in my code?

Stop expecting the JSON in a message. Create the run, take the webhook or poll, check output_error, then read output. Reserve a field for each media file and use SumeMediaFile# rather than a bare string. Set primary_output_key to the field your UI shows.

Do not expect values you sent in input to come back in output: the projection never sees them. For a longer tour of the subset, see Sume Format structured output.

What does a failed projection look like compared with a refusal?

There is no safety refusal to detect. When a run completes but its result does not fit your schema, the receipt carries output_error with a code such as output_schema_unsatisfied, and over the API the run is failed, so status is the first branch. The media is still in artifacts[]. Handle output: null on both a failed and a completed receipt, as the docs' checklist says.

Sources

Related posts

More in Formats

All Formats posts

Written by Sume