From 0df61ac9770d6f544ff7356cd2e8bb2a73c4cdd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Fri, 26 Jun 2026 15:22:15 +0200 Subject: [PATCH 1/9] Add Troubleshooting Gemini API keys section Document why a bad Gemini API key fails silently: createClient does no network call, so auth/access failures surface only via the Live session onerror/onclose callbacks. Covers common causes (wrong/mistyped key, wrong project, API not enabled, region block, native-audio 1008 'leaked'/1011 model-specific rejection) and how to fix. --- docs/integrations/gemini-live-integration.mdx | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/integrations/gemini-live-integration.mdx b/docs/integrations/gemini-live-integration.mdx index bc0f138f..16f456c2 100644 --- a/docs/integrations/gemini-live-integration.mdx +++ b/docs/integrations/gemini-live-integration.mdx @@ -152,7 +152,7 @@ Create a Fishjam agent configured to match the audio format that the Google clie ### Step 3: Connect the Streams -:::warning Encoding +:::warning[Encoding] Fishjam handles raw bytes, while Google GenAI SDKs often expect Base64 strings. Ensure you convert between them correctly as shown below. ::: @@ -278,3 +278,50 @@ Fishjam handles raw bytes, while Google GenAI SDKs often expect Base64 strings. + +## Troubleshooting Gemini API keys + +If your agent joins the room but silently does nothing — no audio, no transcription, and no error — the problem is almost always the Gemini API key. + +:::warning Errors are asynchronous, not thrown +`createClient` is a thin synchronous wrapper around Google's `GoogleGenAI` and makes no network call, so an invalid or unauthorized key is **not** rejected when you create the client. The key is only exercised later, when `live.connect()` opens the Live websocket. Authentication and access failures then surface through the Live session callbacks (`onerror` / `onclose`) — never as a thrown exception. +::: + +Always implement the `onerror` and `onclose` callbacks and log the close code and reason. That is where the real error message appears: + +```ts +import GeminiIntegration from "@fishjam-cloud/js-server-sdk/gemini"; +import { Modality, type LiveServerMessage } from "@google/genai"; + +const genAi = GeminiIntegration.createClient({ + apiKey: process.env.GOOGLE_API_KEY!, +}); +const GEMINI_MODEL = "gemini-2.5-flash-native-audio-preview-12-2025"; +const handleMessage = (msg: LiveServerMessage) => {}; + +// ---cut--- +const session = await genAi.live.connect({ + model: GEMINI_MODEL, + config: { responseModalities: [Modality.AUDIO] }, + callbacks: { + onopen: () => console.log("Gemini Live connected"), + onerror: (e) => console.error("Gemini Live error:", e), + onclose: (e) => + console.error(`Gemini Live closed: code=${e.code} reason=${e.reason}`), + onmessage: handleMessage, + }, +}); +``` + +### Common reasons a key doesn't work + +- Wrong or mistyped key, or a key from the wrong Google Cloud project. +- The Gemini API is not enabled for the key's project. +- Region or country restriction — the Gemini API (or its free tier) is not available in your region. +- Model-specific rejection. The native-audio Live models (e.g. `gemini-2.5-flash-native-audio-preview-*`) can reject an otherwise-valid key: the websocket closes with code `1008` and the message _"Your API key was reported as leaked. Please use another API key."_ (even when the key is not actually leaked), or with code `1011` internal errors. A key that works for other models can still fail here. + +### How to fix + +- Verify or regenerate your key at [Google AI Studio](https://aistudio.google.com/app/apikey). +- Confirm the Gemini API is enabled for the key's project and that your region is supported. +- If you see the `1008` "leaked" message, rotate to a freshly generated key. From 46e67ab02b4a4db68ca87f7882edf0b357ee5858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Fri, 26 Jun 2026 15:55:51 +0200 Subject: [PATCH 2/9] Fix Encoding admonition title in versioned Gemini docs --- .../version-0.26.0/integrations/gemini-live-integration.mdx | 2 +- .../version-0.27.0/integrations/gemini-live-integration.mdx | 2 +- .../version-0.28.0/integrations/gemini-live-integration.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/versioned_docs/version-0.26.0/integrations/gemini-live-integration.mdx b/versioned_docs/version-0.26.0/integrations/gemini-live-integration.mdx index 33518a1e..139b6533 100644 --- a/versioned_docs/version-0.26.0/integrations/gemini-live-integration.mdx +++ b/versioned_docs/version-0.26.0/integrations/gemini-live-integration.mdx @@ -152,7 +152,7 @@ Create a Fishjam agent configured to match the audio format that the Google clie ### Step 3: Connect the Streams -:::warning Encoding +:::warning[Encoding] Fishjam handles raw bytes, while Google GenAI SDKs often expect Base64 strings. Ensure you convert between them correctly as shown below. ::: diff --git a/versioned_docs/version-0.27.0/integrations/gemini-live-integration.mdx b/versioned_docs/version-0.27.0/integrations/gemini-live-integration.mdx index 33518a1e..139b6533 100644 --- a/versioned_docs/version-0.27.0/integrations/gemini-live-integration.mdx +++ b/versioned_docs/version-0.27.0/integrations/gemini-live-integration.mdx @@ -152,7 +152,7 @@ Create a Fishjam agent configured to match the audio format that the Google clie ### Step 3: Connect the Streams -:::warning Encoding +:::warning[Encoding] Fishjam handles raw bytes, while Google GenAI SDKs often expect Base64 strings. Ensure you convert between them correctly as shown below. ::: diff --git a/versioned_docs/version-0.28.0/integrations/gemini-live-integration.mdx b/versioned_docs/version-0.28.0/integrations/gemini-live-integration.mdx index bc0f138f..43a0eeca 100644 --- a/versioned_docs/version-0.28.0/integrations/gemini-live-integration.mdx +++ b/versioned_docs/version-0.28.0/integrations/gemini-live-integration.mdx @@ -152,7 +152,7 @@ Create a Fishjam agent configured to match the audio format that the Google clie ### Step 3: Connect the Streams -:::warning Encoding +:::warning[Encoding] Fishjam handles raw bytes, while Google GenAI SDKs often expect Base64 strings. Ensure you convert between them correctly as shown below. ::: From 76c20184c0210e9debf134ee25bc3b28130b71b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Fri, 26 Jun 2026 16:09:45 +0200 Subject: [PATCH 3/9] Fix danger/info admonition titles to bracket syntax --- docs/_common/agents/remember-to-disconnect.mdx | 2 +- .../version-0.26.0/_common/agents/remember-to-disconnect.mdx | 2 +- versioned_docs/version-0.26.0/tutorials/livestreaming.mdx | 2 +- .../version-0.27.0/_common/agents/remember-to-disconnect.mdx | 2 +- .../version-0.28.0/_common/agents/remember-to-disconnect.mdx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/_common/agents/remember-to-disconnect.mdx b/docs/_common/agents/remember-to-disconnect.mdx index 4960eed1..d1a45cd5 100644 --- a/docs/_common/agents/remember-to-disconnect.mdx +++ b/docs/_common/agents/remember-to-disconnect.mdx @@ -1,3 +1,3 @@ -:::danger Remember to disconnect your agents! +:::danger[Remember to disconnect your agents!] It's important to disconnect agents, because **every connected agent generates usage** just as a normal peer. ::: diff --git a/versioned_docs/version-0.26.0/_common/agents/remember-to-disconnect.mdx b/versioned_docs/version-0.26.0/_common/agents/remember-to-disconnect.mdx index 4960eed1..d1a45cd5 100644 --- a/versioned_docs/version-0.26.0/_common/agents/remember-to-disconnect.mdx +++ b/versioned_docs/version-0.26.0/_common/agents/remember-to-disconnect.mdx @@ -1,3 +1,3 @@ -:::danger Remember to disconnect your agents! +:::danger[Remember to disconnect your agents!] It's important to disconnect agents, because **every connected agent generates usage** just as a normal peer. ::: diff --git a/versioned_docs/version-0.26.0/tutorials/livestreaming.mdx b/versioned_docs/version-0.26.0/tutorials/livestreaming.mdx index a160ea3e..b00a9388 100644 --- a/versioned_docs/version-0.26.0/tutorials/livestreaming.mdx +++ b/versioned_docs/version-0.26.0/tutorials/livestreaming.mdx @@ -21,7 +21,7 @@ In this tutorial we explain how to publish and view streams with the client SDKs If you prefer to use WHIP or WHEP directly, then you should read [WHIP/WHEP with Fishjam](../how-to/backend/whip-whep). ::: -:::info Cost-effective audio livestreams +:::info[Cost-effective audio livestreams] Fishjam supports audio-only livestreams at a reduced cost of $0.20 per 1000 minutes of the streamer and each listener (compared to $0.80 for video livestreams). This is ideal for podcasts, radio-style broadcasts, or any scenario where video isn't necessary. See [Audio-only calls](../how-to/backend/audio-only-calls) for more information. diff --git a/versioned_docs/version-0.27.0/_common/agents/remember-to-disconnect.mdx b/versioned_docs/version-0.27.0/_common/agents/remember-to-disconnect.mdx index 4960eed1..d1a45cd5 100644 --- a/versioned_docs/version-0.27.0/_common/agents/remember-to-disconnect.mdx +++ b/versioned_docs/version-0.27.0/_common/agents/remember-to-disconnect.mdx @@ -1,3 +1,3 @@ -:::danger Remember to disconnect your agents! +:::danger[Remember to disconnect your agents!] It's important to disconnect agents, because **every connected agent generates usage** just as a normal peer. ::: diff --git a/versioned_docs/version-0.28.0/_common/agents/remember-to-disconnect.mdx b/versioned_docs/version-0.28.0/_common/agents/remember-to-disconnect.mdx index 4960eed1..d1a45cd5 100644 --- a/versioned_docs/version-0.28.0/_common/agents/remember-to-disconnect.mdx +++ b/versioned_docs/version-0.28.0/_common/agents/remember-to-disconnect.mdx @@ -1,3 +1,3 @@ -:::danger Remember to disconnect your agents! +:::danger[Remember to disconnect your agents!] It's important to disconnect agents, because **every connected agent generates usage** just as a normal peer. ::: From ef2fd70aad04cdf53ead28ffeffb1232ac400bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Fri, 26 Jun 2026 16:10:59 +0200 Subject: [PATCH 4/9] Fix Errors admonition title to bracket syntax --- docs/integrations/gemini-live-integration.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/gemini-live-integration.mdx b/docs/integrations/gemini-live-integration.mdx index 16f456c2..16e20f8d 100644 --- a/docs/integrations/gemini-live-integration.mdx +++ b/docs/integrations/gemini-live-integration.mdx @@ -283,7 +283,7 @@ Fishjam handles raw bytes, while Google GenAI SDKs often expect Base64 strings. If your agent joins the room but silently does nothing — no audio, no transcription, and no error — the problem is almost always the Gemini API key. -:::warning Errors are asynchronous, not thrown +:::warning[Errors are asynchronous, not thrown] `createClient` is a thin synchronous wrapper around Google's `GoogleGenAI` and makes no network call, so an invalid or unauthorized key is **not** rejected when you create the client. The key is only exercised later, when `live.connect()` opens the Live websocket. Authentication and access failures then surface through the Live session callbacks (`onerror` / `onclose`) — never as a thrown exception. ::: From 416a21d90ca42d814605165085abf6f2c5714e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Fri, 26 Jun 2026 17:14:40 +0200 Subject: [PATCH 5/9] Scope Gemini key-error admonition: auth fails via callbacks, connect() can still reject (Copilot) --- docs/integrations/gemini-live-integration.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/integrations/gemini-live-integration.mdx b/docs/integrations/gemini-live-integration.mdx index 16e20f8d..a871db1e 100644 --- a/docs/integrations/gemini-live-integration.mdx +++ b/docs/integrations/gemini-live-integration.mdx @@ -283,8 +283,8 @@ Fishjam handles raw bytes, while Google GenAI SDKs often expect Base64 strings. If your agent joins the room but silently does nothing — no audio, no transcription, and no error — the problem is almost always the Gemini API key. -:::warning[Errors are asynchronous, not thrown] -`createClient` is a thin synchronous wrapper around Google's `GoogleGenAI` and makes no network call, so an invalid or unauthorized key is **not** rejected when you create the client. The key is only exercised later, when `live.connect()` opens the Live websocket. Authentication and access failures then surface through the Live session callbacks (`onerror` / `onclose`) — never as a thrown exception. +:::warning[API key errors surface asynchronously] +`createClient` is a thin synchronous wrapper around Google's `GoogleGenAI` and makes no network call, so an invalid or unauthorized key is **not** rejected when you create the client. The key is only exercised later, when `live.connect()` opens the Live websocket. Authentication and access failures then typically surface through the Live session callbacks (`onerror` / `onclose`) rather than as a thrown exception. (Other problems, such as a malformed config, can still reject the awaited `connect()` call — keep a `try/catch` around it too.) ::: Always implement the `onerror` and `onclose` callbacks and log the close code and reason. That is where the real error message appears: From cf5a309d63a14f311b50c30481de78877e1f6201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Mon, 29 Jun 2026 11:10:48 +0200 Subject: [PATCH 6/9] Add Gemini checkCredentials to snippet; bump js-server-sdk submodule to fce-3076 (e26aca8) --- docs/integrations/gemini-live-integration.mdx | 2 ++ packages/js-server-sdk | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/integrations/gemini-live-integration.mdx b/docs/integrations/gemini-live-integration.mdx index a871db1e..552ffaef 100644 --- a/docs/integrations/gemini-live-integration.mdx +++ b/docs/integrations/gemini-live-integration.mdx @@ -300,6 +300,8 @@ const GEMINI_MODEL = "gemini-2.5-flash-native-audio-preview-12-2025"; const handleMessage = (msg: LiveServerMessage) => {}; // ---cut--- +await GeminiIntegration.checkCredentials(genAi); + const session = await genAi.live.connect({ model: GEMINI_MODEL, config: { responseModalities: [Modality.AUDIO] }, diff --git a/packages/js-server-sdk b/packages/js-server-sdk index 0e8fe3e0..e26aca88 160000 --- a/packages/js-server-sdk +++ b/packages/js-server-sdk @@ -1 +1 @@ -Subproject commit 0e8fe3e0eb13127bade0b761ca254aed03cb2c46 +Subproject commit e26aca88e99b59bb44ac79d3f392dfcd0ecc1f59 From a5d263b643afe6534b9b2cab54f741cf80412320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Mon, 29 Jun 2026 11:46:32 +0200 Subject: [PATCH 7/9] Reconcile key-error admonition with checkCredentials snippet (Copilot) The admonition said the key is only exercised at live.connect() and that auth failures surface via callbacks rather than thrown, which contradicted the snippet calling checkCredentials() (a throwing network check) first. Scope the connect-path claim to the unverified case and introduce checkCredentials as the explicit fail-fast check. --- docs/integrations/gemini-live-integration.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/integrations/gemini-live-integration.mdx b/docs/integrations/gemini-live-integration.mdx index 552ffaef..bf30bac3 100644 --- a/docs/integrations/gemini-live-integration.mdx +++ b/docs/integrations/gemini-live-integration.mdx @@ -284,10 +284,10 @@ Fishjam handles raw bytes, while Google GenAI SDKs often expect Base64 strings. If your agent joins the room but silently does nothing — no audio, no transcription, and no error — the problem is almost always the Gemini API key. :::warning[API key errors surface asynchronously] -`createClient` is a thin synchronous wrapper around Google's `GoogleGenAI` and makes no network call, so an invalid or unauthorized key is **not** rejected when you create the client. The key is only exercised later, when `live.connect()` opens the Live websocket. Authentication and access failures then typically surface through the Live session callbacks (`onerror` / `onclose`) rather than as a thrown exception. (Other problems, such as a malformed config, can still reject the awaited `connect()` call — keep a `try/catch` around it too.) +`createClient` is a thin synchronous wrapper around Google's `GoogleGenAI` and makes no network call, so it does **not** validate your key — an invalid or unauthorized key is not rejected when you create the client. Unless you verify it explicitly (see below), the key is first exercised when `live.connect()` opens the Live websocket, and authentication or access failures then typically surface through the Live session callbacks (`onerror` / `onclose`) rather than as a thrown exception. (Other problems, such as a malformed config, can still reject the awaited `connect()` call — keep a `try/catch` around it too.) ::: -Always implement the `onerror` and `onclose` callbacks and log the close code and reason. That is where the real error message appears: +To catch a bad key up front, call `checkCredentials` before connecting: it makes a single network request and throws if the key is rejected, so misconfiguration fails fast instead of silently. Still implement the `onerror` and `onclose` callbacks and log the close code and reason — that is where model-specific rejections (see below) and other Live failures appear: ```ts import GeminiIntegration from "@fishjam-cloud/js-server-sdk/gemini"; From 7781621dbe5dd56ff9886b352efc44d0359cc607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Tue, 30 Jun 2026 10:26:22 +0200 Subject: [PATCH 8/9] Update js-sdk --- packages/js-server-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js-server-sdk b/packages/js-server-sdk index e26aca88..c2dd85ec 160000 --- a/packages/js-server-sdk +++ b/packages/js-server-sdk @@ -1 +1 @@ -Subproject commit e26aca88e99b59bb44ac79d3f392dfcd0ecc1f59 +Subproject commit c2dd85ecb40e54ce300b8c1b8f811dbdba726213 From 6451bfc1afe6471e4dbfae88a100fa51f9416e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= <48837433+roznawsk@users.noreply.github.com> Date: Wed, 1 Jul 2026 14:55:12 +0200 Subject: [PATCH 9/9] Update docs/integrations/gemini-live-integration.mdx Co-authored-by: adrian <33912477+czerwiukk@users.noreply.github.com> --- docs/integrations/gemini-live-integration.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/gemini-live-integration.mdx b/docs/integrations/gemini-live-integration.mdx index bf30bac3..6548433b 100644 --- a/docs/integrations/gemini-live-integration.mdx +++ b/docs/integrations/gemini-live-integration.mdx @@ -281,7 +281,7 @@ Fishjam handles raw bytes, while Google GenAI SDKs often expect Base64 strings. ## Troubleshooting Gemini API keys -If your agent joins the room but silently does nothing — no audio, no transcription, and no error — the problem is almost always the Gemini API key. +If your agent joins the room but silently does nothing — no audio, no transcription, and no error — the issue is often related to the Gemini API key. :::warning[API key errors surface asynchronously] `createClient` is a thin synchronous wrapper around Google's `GoogleGenAI` and makes no network call, so it does **not** validate your key — an invalid or unauthorized key is not rejected when you create the client. Unless you verify it explicitly (see below), the key is first exercised when `live.connect()` opens the Live websocket, and authentication or access failures then typically surface through the Live session callbacks (`onerror` / `onclose`) rather than as a thrown exception. (Other problems, such as a malformed config, can still reject the awaited `connect()` call — keep a `try/catch` around it too.)