If your code calls Google's Gemini Interactions API (/v1beta/interactions), there is a short, silent window opening on May 26, 2026. On that date, Google flips the default response schema. interaction.outputs becomes interaction.steps, response_mime_type folds into a polymorphic response_format, image_config moves out of generation_config, and the streaming event names you wired listeners to all rename. The legacy schema still works if you explicitly send Api-Revision: 2026-05-07 — until June 8, 2026, when it's removed for good.
Most of these surfaces don't fail loudly. They fail by returning undefined, by ignoring a config field, or by emitting an SSE event your handler doesn't recognize. The first signal is usually a downstream consumer noticing that the model's reply is empty, or that the tool dispatch loop stopped firing, or that the generated image came back in the wrong aspect ratio.
The Timeline
-
May 7, 2026 — opt-in begins. New SDKs (Python ≥2.0.0, JS ≥2.0.0) ship; REST clients can opt in with
Api-Revision: 2026-05-20. -
May 26, 2026 — default flips. Any REST call without an
Api-Revisionheader gets the new schema. SDKs older than 2.0.0 keep getting the legacy shape (for now). -
June 8, 2026 — legacy schema removed permanently. The
Api-Revision: 2026-05-07opt-out stops working. Older SDKs that depend onoutputsstart breaking.
The dangerous window is May 26 → June 8: anything pinned to the legacy header keeps working, but anything calling REST without a header — most ad-hoc integrations and a lot of internal tooling — gets the new shape silently.
What Actually Changes
1. outputs[] → steps[] (the read path you almost certainly have)
Legacy response:
{
"id": "int_123",
"role": "model",
"outputs": [
{ "type": "text", "text": "Why did the chicken cross the road?" }
]
}
New response:
{
"id": "int_123",
"steps": [
{
"type": "model_output",
"content": [
{ "type": "text", "text": "Why did the chicken cross the road?" }
]
}
]
}
The shape change cascades:
-
outputsis gone.interaction.outputsisundefined(JS) or raisesKeyError(Python dict) or fails attribute access (typed clients). - The
textfield moves one level deeper, behindcontent[0]. In Python:interaction.outputs[-1].text→interaction.steps[-1].content[0].text. - A new top-level step type,
user_input, appears inGETresponses (full timeline). Code that iteratesoutputsassuming every entry is model-emitted will now also see the user's prompt as a step — fine if you filter, broken if you concatenate everything you see.
2. response_mime_type → polymorphic response_format
Legacy request for JSON output:
{
"model": "gemini-3-flash-preview",
"input": "Summarize this article.",
"response_mime_type": "application/json",
"response_format": {
"type": "object",
"properties": { "summary": { "type": "string" } }
}
}
New request:
{
"model": "gemini-3-flash-preview",
"input": "Summarize this article.",
"response_format": {
"type": "text",
"mime_type": "application/json",
"schema": {
"type": "object",
"properties": { "summary": { "type": "string" } }
}
}
}
response_mime_type at the top level is gone — it folds into response_format.mime_type. The schema moves into response_format.schema. The discriminator that picks text vs image vs audio is response_format.type. After May 26, the server silently ignores the legacy top-level response_mime_type field; your JSON-mode call quietly stops being JSON-mode.
3. image_config moves out of generation_config
Legacy:
{
"model": "gemini-3-flash-preview",
"input": "Generate an image of a sunset over the ocean.",
"generation_config": {
"image_config": { "aspect_ratio": "1:1", "image_size": "1K" }
}
}
New:
{
"model": "gemini-3-flash-preview",
"input": "Generate an image of a sunset over the ocean.",
"response_format": {
"type": "image",
"mime_type": "image/jpeg",
"aspect_ratio": "1:1",
"image_size": "1K"
}
}
The fields are gone from generation_config. Server-side they're not read from that location anymore. The image still generates — just at whatever the new default aspect ratio and size are. Your "always render 1:1 thumbnails" job starts shipping 16:9 widescreens with no log line to explain it.
4. Streaming SSE event names rename
| Legacy | New |
|---|---|
interaction.start |
interaction.created |
content.start |
step.start |
content.delta |
step.delta |
content.stop |
step.stop |
interaction.complete |
interaction.completed |
interaction.status_update |
interaction.in_progress, interaction.requires_action, … |
If you wired event listeners with a switch on event name or with EventSource handlers like es.addEventListener('content.delta', …), after May 26 those events never fire. The stream still arrives — step.delta frames pour in — but your callback isn't subscribed to them. The user-facing symptom is "the response just hangs."
5. Function calls live in steps, not outputs
Legacy tool-call response:
{
"id": "int_001",
"status": "requires_action",
"outputs": [
{ "type": "function_call", "id": "fc_1", "name": "get_weather", "arguments": { "location": "Boston, MA" } }
]
}
New:
{
"id": "int_001",
"status": "requires_action",
"steps": [
{ "type": "function_call", "id": "fc_1", "name": "get_weather", "arguments": { "location": "Boston, MA" } }
]
}
Tool dispatch loops typically iterate outputs, find type === "function_call" entries, invoke the handler, then submit results back. After the flip, outputs is undefined, so the loop iterates nothing, finds no function calls, returns the unaltered requires_action interaction to the caller, and the agent stalls. No exception — just an agent that "didn't decide to call a tool this turn." The same trap applies to google_search_call, google_search_result, and thought step types, all of which now live under steps.
6. The thought step shape changes too
Legacy thought was minimal:
{ "type": "thought", "signature": "abc123..." }
New thought carries a structured summary:
{
"type": "thought",
"summary": [{ "type": "text", "text": "I need to check the weather in Boston..." }],
"signature": "abc123..."
}
Any logging or audit code reading thought.text (which never existed but was a common guess) silently gets undefined. Code that round-trips thought back to Gemini for stateless continuation now has to preserve the summary array — drop it and the model loses its scratchpad.
The Silent Surfaces
Walking through the six places this fails quietly:
-
Response readers —
interaction.outputs[-1].text→undefined(orKeyError/AttributeError). Templated chat UIs render the empty string. Logs show "model returned no text" but the model did return text; you just stopped reading it. -
JSON-mode validators — Top-level
response_mime_type: "application/json"is silently ignored after May 26. The model still tries to follow the schema if you also send aresponse_format, but enforcement weakens. Free-form responses slip pastJSON.parseon the client and crash downstream. -
Image params —
generation_config.image_configis silently dropped. Aspect ratio and size revert to defaults. Thumbnails come back at the wrong dimensions; layout breaks; humans complain. -
Streaming listeners —
addEventListener('content.delta', …)never fires. The stream finishes; your token buffer stays empty; the UI shows the spinner forever. -
Tool dispatchers — function-call loops keyed on
outputs[].type === 'function_call'find no calls. The agent silently no-ops on a turn the model intended to use tools. -
Stateless history round-trips — passing the prior
outputsarray as input to the next request after May 26 → the server doesn't recognize it as a valid input shape (or worse, partially interprets it). Conversation history detaches; the model loses context with no error.
How To Detect It Before May 26
1. Opt in now and run your test suite. This is the cheapest signal. Add the header to your dev/staging environment:
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions?key=$GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-H "Api-Revision: 2026-05-20" \
-d '{ ... }'
…or upgrade to Python ≥2.0.0 / JS ≥2.0.0. Anything that broke in dev under the new schema will break in prod on May 26. The header buys you a controlled fire drill in staging before the default change forces it on you in production.
2. Grep for the legacy field paths. All of these are exposed:
-
.outputs(esp..outputs[,.outputs[-1],outputs.length,outputs.map) -
response_mime_type(anywhere in request construction) -
image_config(insidegeneration_configblocks) -
'content.delta','content.start','content.stop','interaction.complete','interaction.start'(SSE event handlers) -
type === 'function_call'inside loops overoutputs
Each match is a migration site. The streaming event names are the easiest to miss — a single addEventListener call buried in a chat UI can take down the whole streaming path.
3. Add a schema check on the response. Until you've migrated, log a warning if response.outputs is undefined and response.steps is present. After June 8 the legacy header stops working, so this assertion becomes a permanent canary for "are we accidentally hitting an unmigrated client path."
4. Pin Api-Revision: 2026-05-07 only as a temporary safety net. It buys you until June 8 — about two weeks past the default flip. Use it to keep prod alive while you migrate; don't use it as the long-term answer. The header stops being honored on June 8.
Why This Is Worth Paying Attention To
The same code path that ran for a year against outputs[-1].text keeps compiling, keeps passing type checks (if your types come from an older SDK), keeps returning a 200. The model itself is unchanged. The bytes on the wire are different bytes in different places. None of the usual signals — HTTP status, exception, SDK error — fire.
The pattern across all six silent surfaces is the same: a vendor moves a value to a new path, and old code reading the old path gets back a value (undefined, the default, the empty array) that's a valid answer to a different question. The wire is silent because the language is silent: undefined is a real JS value; an empty array is a real iteration; the default aspect ratio is a real image.
If you run anything on Gemini's Interactions API, the cheapest move you can make right now is to add the Api-Revision: 2026-05-20 header in staging and let the tests find what's exposed. However many days are left before May 26, spending them on a controlled staging drill beats discovering this from a confused user report after the default flips.
Top comments (0)