The two schema fields accept different keywords
In my tests, the OpenAPI-style responseSchema field of the Gemini API returned HTTP 400 for additionalProperties, $schema, $id, $ref and a type array, while the newer responseJsonSchema field accepted all of them. Two keywords were accepted without constraining the output reliably (allOf on both models I tried, oneOf on one), so a valid JSON reply can still break your schema.
- If you get
Unknown name "additionalProperties" ... Cannot find field, you are sending a JSON Schema keyword toresponseSchema. Remove it or switch toresponseJsonSchema. nullable: trueworks inresponseSchema;"type": ["string", "null"]works only inresponseJsonSchema.- Accepted does not mean enforced:
allOfand, on one model,oneOfproduced output that failed my schema check. - Validate the reply in your own code every time, whatever the API accepted.
How I tested
On 9 October 2026 I sent small generateContent requests to the Gemini API with a free-tier key: gemini-3.6-flash for the responseSchema field, gemini-3.7-flash for the responseJsonSchema field, and gemini-3.5-flash-lite for the extra experiments at the end. The prompt was the same each time (extract a user id and a status from one sentence), temperature was 0, and every schema was a small variation of one base object. I then checked each reply against the schema I had sent with Python's jsonschema 4.25.0 (version 7 validator). The free tier allows 20 requests per model per day, so every case is a single sample: it shows what the API did once, not how often.
body = {
"contents": [{"role": "user", "parts": [{"text": "Extract the profile: user id 9912, email admin@example.com, status active."}]}],
"generationConfig": {
"temperature": 0,
"responseMimeType": "application/json",
"responseSchema": schema, # or "responseJsonSchema": schema
},
}
# POST to https://generativelanguage.googleapis.com/v1beta/models/<model>:generateContent
# then: jsonschema.Draft7Validator(schema).validate(json.loads(reply_text))
Results with responseSchema (gemini-3.6-flash)
| Schema feature | API response | Reply matched the schema? |
|---|---|---|
Base object with enum and required | HTTP 200 | Yes |
additionalProperties: false | HTTP 400: Unknown name "additionalProperties" ... Cannot find field. | n/a |
$schema and $id | HTTP 400: Unknown name "$schema" ... Cannot find field. | n/a |
$ref to $defs | HTTP 400: Unknown name "$ref" ... | n/a |
"type": ["string", "null"] | HTTP 400: Proto field is not repeating | n/a |
nullable: true | HTTP 200 | Yes (returned null) |
title, description | HTTP 200 | Yes |
anyOf (string or integer) | HTTP 200 | Yes |
oneOf (string or integer) | HTTP 200 | No: returned an object where the schema allows a string or an integer |
allOf at the root | HTTP 200 | No: returned different key names (user_id, not userId) |
pattern, format: date-time, minimum/maximum, default | HTTP 200 | Yes (one sample each) |
Results with responseJsonSchema (gemini-3.7-flash)
I ran the same cases through the responseJsonSchema field. Everything that failed with HTTP 400 above was accepted here.
| Schema feature | API response | Reply matched the schema? |
|---|---|---|
additionalProperties: false | HTTP 200 | Yes |
$schema and $id | HTTP 200 | Yes |
$ref to $defs | HTTP 200 | Yes (a one-property schema) |
"type": ["string", "null"] and nullable: true | HTTP 200 | Yes |
anyOf, oneOf | HTTP 200 | Yes |
allOf at the root | HTTP 200 | No: required key userId missing, other key names returned |
pattern, format, minimum/maximum, default | HTTP 200 | Yes (one sample each) |
The reply to the oneOf case matched the schema on this model, although it did not on the other one. That difference between models is the practical lesson: a keyword the API accepts can behave differently from one model to the next.
Three things I expected to fail and that did not
- A system instruction that asks for an explanation. I told the model to explain its reasoning step by step before answering while a schema was set. The reply was still valid JSON that matched the schema (on
gemini-3.7-flashand ongemini-3.5-flash-lite). - Extra fields. I asked for "any other helpful fields" with and without
additionalProperties: false(ongemini-3.5-flash-lite, and without the keyword also ongemini-3.7-flash). Every reply contained only the schema's two properties. One sample each, so this does not prove the keyword is unnecessary. - Deep nesting. Objects nested 5 levels and 12 levels deep were both accepted and filled correctly (
gemini-3.5-flash-lite). Google's documentation only says that very large or deeply nested schemas may be rejected, without a number.
What to do with this
- Pick the field on purpose. If your schema comes from Pydantic or Zod and contains
additionalProperties,$refor type arrays, useresponseJsonSchema. If you stay onresponseSchema, strip those keywords or the request fails with HTTP 400 before any generation. - Treat HTTP 400 and a wrong reply as different problems. A 400 names the offending key in the message, so read it. A 200 with the wrong shape needs a validator in your code.
- Avoid
allOfand be careful withoneOf. Flatten the schema into one object with optional fields, or useanyOf, which behaved in my tests. - Validate and retry. Parse the reply, validate it with
jsonschemaor your Pydantic model, and retry once with the validation error appended to the prompt when it fails.
What this test did not cover
Each case ran once at temperature 0 on a very small schema, so it says nothing about failure rates or about large schemas. I did not test Gemini 3.1 Pro (the free tier has a quota of zero for it), the Node.js SDK, Vertex AI, streaming, or the response_format parameter that Google's current structured-output page documents; I tested the older generateContent fields. Model names change quickly, so rerun a handful of these cases on the model you use.
Sources and Further Reading
- Structured outputs — Google AI for Developers
- Rate limits — Google AI for Developers (free-tier daily limits)
- jsonschema — Python validator used for the checks
