Gemini recursive schema with $ref "#": what Sume accepts instead
Gemini's docs show an org-chart schema that recurses with $ref "#". Sume rejects that root reference; recurse through a named $defs entry instead.

A Gemini schema that recurses with "$ref": "#" will not pass Sume's output_schema check. Sume rejects $ref: "#" with 400 output_schema_invalid; the same recursion works if you move the node into a named $defs entry at the root and point $ref at #/$defs/<name>.
Google's Structured outputs page includes a "Recursive Structures" example, an organization chart where each employee has a reports array whose items is { "$ref": "#" } (read 2026-10-02). Porting that schema to a Sume Format run or an Agent Completion needs three edits, listed below. Nothing runs and nothing is charged when the schema is rejected, so a mistake costs one request.
What does the Gemini example do?
The page defines name, employee_id and reports, lists all three in required, and makes reports an array whose items refer back to the whole schema with $ref: "#". The Python version generates the schema from a Pydantic model with a self-referencing Employee type.
That shape is legal on Gemini's side. It is not legal on Sume's, because Sume's Structured output page lists exactly two $ref targets: #/$defs/* and SumeMediaFile#. It says $ref: "#" is rejected even though OpenAI's strict mode permits root recursion that way.
How do I write the same tree on Sume?
Declare the node once under $defs at the root of the schema document, then let that definition refer to itself. Sume's page says recursion through a named definition is fine, and that the depth limit counts literal nesting in the document, so a self-referential definition does not consume it.
Two further rules apply to every object in the schema, including the one inside $defs: additionalProperties must be false, and every declared property must be in required. The root type must be exactly object, so the tree sits under a property such as team.
{
"name": "acme/org-chart/v1",
"strict": true,
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["team"],
"properties": { "team": { "$ref": "#/$defs/employee" } },
"$defs": {
"employee": {
"type": "object",
"additionalProperties": false,
"required": ["name", "employee_id", "reports"],
"properties": {
"name": { "type": "string" },
"employee_id": { "type": "integer" },
"reports": { "type": "array", "items": { "$ref": "#/$defs/employee" } }
}
}
}
}
}How do the two schema dialects compare?
The rows below compare only what each page states.
| Rule | Gemini page | Sume docs |
|---|---|---|
| Root recursion | Example uses $ref: "#" in items | $ref: "#" rejected |
| Named recursion | Not shown on the page | Allowed through #/$defs/* |
| Root type | Example root is an object | Must be exactly object |
| Closed objects | additionalProperties accepts a boolean or a schema | additionalProperties: false on every object |
| Optional fields | Add "null" to the type array | Nullable union; every property stays in required |
What else should I check before sending it?
Sume describes output_schema as a contract for how a finished run is read back, not a constraint on the run itself. A recursive tree is therefore filled from the run's closing text and media, or by the run submitting the object itself; the receipt's filled_by says which. If a field is something only your input contained, the projection path cannot see it.
Read the details.violations[] array on a 400. Each entry has a path, a stable rule token and a message, and every problem is reported in one response, so one failed request is enough to fix the schema.
- Move the recursive node under root
$defs. - Replace each
$ref: "#"with#/$defs/<name>. - Add
additionalProperties: falseto every object, and list every property inrequired. - Wrap the tree so the root is an object.
Sources
Related posts
More in Developers
- Check GET /v1/balance before a Sume bulk run: failures land per item
A bulk create returns 202 even if the wallet cannot fund every child; unfunded items fail one by one. Compare GET /v1/balance with your spend caps first.
- Smoke-test a new Sume API key with GET /v1/me before revoking the old
GET /v1/me returns the key's id, prefix and scopes. A TypeScript script fails the deploy if the new key lacks formats:write, so you revoke the old key after.
- GPT Image 2.5 edit changed shape? Use aspect_ratio auto on Sume
On Sume edit and image-to-image calls, aspect_ratio auto is not the same as leaving the field out. How to read the model descriptor and keep the photo's shape.
- GPT Image 2.5 image_size rules: validate in Python before you send
Custom image_size on GPT Image 2.5 needs edges in multiples of 16, max edge 3840, aspect 3:1 or less, and 655,360 to 8,294,400 pixels. A Python checker for it.
Written by Sume