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
- 20 ad variations from one product image with an image API
Make 20 static ad variations from one product photo: five headline prompts times n=4, async jobs, and a model whose catalog allows n=4.
- 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.
- GitHub wants a 2xx in 10 seconds: Sume times out at 10 too
GitHub says respond 2XX within 10 seconds. Sume also gives each delivery attempt 10 seconds, so store the event first and do the work after you answer.
Written by Sume