DEV Community

FlareCanary
FlareCanary

Posted on

Gemini's Interactions API default flips May 26 — your interaction.outputs reads will go undefined and tool calls silently stop

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, 2026default flips. Any REST call without an Api-Revision header 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-07 opt-out stops working. Older SDKs that depend on outputs start 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?" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

New response:

{
  "id": "int_123",
  "steps": [
    {
      "type": "model_output",
      "content": [
        { "type": "text", "text": "Why did the chicken cross the road?" }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The shape change cascades:

  • outputs is gone. interaction.outputs is undefined (JS) or raises KeyError (Python dict) or fails attribute access (typed clients).
  • The text field moves one level deeper, behind content[0]. In Python: interaction.outputs[-1].textinteraction.steps[-1].content[0].text.
  • A new top-level step type, user_input, appears in GET responses (full timeline). Code that iterates outputs assuming 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" } }
  }
}
Enter fullscreen mode Exit fullscreen mode

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" } }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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" }
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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" } }
  ]
}
Enter fullscreen mode Exit fullscreen mode

New:

{
  "id": "int_001",
  "status": "requires_action",
  "steps": [
    { "type": "function_call", "id": "fc_1", "name": "get_weather", "arguments": { "location": "Boston, MA" } }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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..." }
Enter fullscreen mode Exit fullscreen mode

New thought carries a structured summary:

{
  "type": "thought",
  "summary": [{ "type": "text", "text": "I need to check the weather in Boston..." }],
  "signature": "abc123..."
}
Enter fullscreen mode Exit fullscreen mode

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 readersinteraction.outputs[-1].textundefined (or KeyError/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 a response_format, but enforcement weakens. Free-form responses slip past JSON.parse on the client and crash downstream.
  • Image paramsgeneration_config.image_config is silently dropped. Aspect ratio and size revert to defaults. Thumbnails come back at the wrong dimensions; layout breaks; humans complain.
  • Streaming listenersaddEventListener('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 outputs array 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 '{ ... }'
Enter fullscreen mode Exit fullscreen mode

…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 (inside generation_config blocks)
  • 'content.delta', 'content.start', 'content.stop', 'interaction.complete', 'interaction.start' (SSE event handlers)
  • type === 'function_call' inside loops over outputs

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)