---
title: "Gemini"
description: "Call the models on pirouter that accept the Gemini protocol with the Google GenAI SDK or REST. This page covers generateContent and streamGenerateContent, the request header the SDK needs, why sending only x-goog-api-key returns 401, and how pirouter differs from Google's own behavior."
canonical: "https://pirouter.ai/docs/protocols/gemini"
last-updated: "2026-10-17"
lang: "en"
source: "content/docs/en/(start)/protocols/gemini.mdx"
---

# Gemini

Call the models on pirouter that accept the Gemini protocol with the Google GenAI SDK or REST. This page covers generateContent and streamGenerateContent, the request header the SDK needs, why sending only x-goog-api-key returns 401, and how pirouter differs from Google's own behavior.

Call the models on pirouter that accept the Gemini protocol with the Google GenAI SDK or REST. One thing must change compared with calling Google directly: the key goes in the `Authorization` header.

## Call it over REST

The Gemini protocol has two actions on one endpoint:

| Action                                              | Use                              |
| --------------------------------------------------- | -------------------------------- |
| `POST /v1beta/models/{model}:generateContent`       | Returns the whole answer at once |
| `POST /v1beta/models/{model}:streamGenerateContent` | Streams the answer               |

The model ID goes in the path as the full `vendor/model`, with the slash kept as is, for example `/v1beta/models/google/gemini-3.5-flash:generateContent`. pirouter does not add a vendor prefix for you.

cURL:

```bash
curl "https://api.pirouter.ai/v1beta/models/google/gemini-3.5-flash:generateContent" \
  -H "Authorization: Bearer $PIROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{ "parts": [{ "text": "Hello!" }] }]
  }'
```

Python:

```python
import os
from google import genai
from google.genai import types

client = genai.Client(
    api_key=os.environ["PIROUTER_API_KEY"],
    http_options=types.HttpOptions(
        base_url="https://api.pirouter.ai",
        headers={"Authorization": f"Bearer {os.environ['PIROUTER_API_KEY']}"},
    ),
)

response = client.models.generate_content(
    model="google/gemini-3.5-flash",
    contents="Hello!",
)
print(response.text)
```

TypeScript:

```ts
import { GoogleGenAI } from '@google/genai'

const ai = new GoogleGenAI({
  apiKey: process.env.PIROUTER_API_KEY,
  httpOptions: {
    baseUrl: 'https://api.pirouter.ai',
    headers: { Authorization: `Bearer ${process.env.PIROUTER_API_KEY}` },
  },
})

const response = await ai.models.generateContent({
  model: 'google/gemini-3.5-flash',
  contents: 'Hello!',
})
console.log(response.text)
```

You should see a JSON response with the answer in `candidates[0].content.parts[0].text`. The request shows up in the console's **Logs**.

To stream, replace `generateContent` with `streamGenerateContent`. The body stays the same and needs no `stream` field:

```bash
curl -N "https://api.pirouter.ai/v1beta/models/google/gemini-3.5-flash:streamGenerateContent" \
  -H "Authorization: Bearer $PIROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{ "parts": [{ "text": "Hello!" }] }]
  }'
```

You should see a series of `data: ` events, each with part of the answer in `candidates`. The last event carries `finishReason` and `usageMetadata`, and no separate end marker follows. The response is this event stream with or without `?alt=sse`.

pirouter only replaces the model in the path with the provider's model name. The body is forwarded as is, apart from removing routing fields such as `provider`.

## Use the Google GenAI SDK

Change two things when you create the client:

1. Set the base URL to `https://api.pirouter.ai`. Do not set an API version; the SDK adds `/v1beta` by default.
2. Add an `Authorization: Bearer` header under `headers` in `http_options` (`httpOptions` in TypeScript). Pass the SDK's `api_key` as usual.

The Python and TypeScript tabs above are written this way.

## Why sending only x-goog-api-key returns 401

pirouter reads the key only from `Authorization` and `X-Api-Key`. It does not read `x-goog-api-key`, nor the `?key=` query parameter, and removes both before forwarding. So relying on the SDK's default header, or following Google's REST example:

```bash
curl "https://api.pirouter.ai/v1beta/models/google/gemini-3.5-flash:generateContent" \
  -H "x-goog-api-key: $PIROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{ "parts": [{ "text": "Hello!" }] }]
  }'
```

returns `401`:

```json
{
  "error": {
    "message": "You didn't provide an API key. Provide it in an Authorization header using Bearer auth (Authorization: Bearer YOUR_KEY).",
    "type": "invalid_request_error",
    "code": "invalid_api_key"
  }
}
```

Put the key in `Authorization: Bearer` and the request goes through. When `Authorization` and `x-goog-api-key` are both present, `Authorization` wins, so the `x-goog-api-key` the SDK adds by default can stay. The rules are in [Send the key with a request](https://pirouter.ai/docs/authentication.md#send-the-key-with-a-request).

## Which models you can use

The Google Gemini family accepts the Gemini protocol, including several image generation models. To check one model, look for `gemini` in the `protocols` of `GET /v1/models/{vendor}/{model}/endpoints`, or see each model's protocols in the [model list](https://pirouter.ai/models). For the image models, see [Generate and edit images](https://pirouter.ai/docs/images.md).

## How pirouter differs from Google

| Area                           | What pirouter does                                                                                                                                                                                           |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authentication                 | `x-goog-api-key` and `?key=` are not read; see above                                                                                                                                                         |
| `modelVersion` in the response | Rewritten to the pirouter model ID (`vendor/model`). The name the provider reported is in the `X-Pi-Upstream-Model` response header                                                                          |
| Upstream request ID            | Some providers return no request ID, and then the response has no `X-Pi-Upstream-Request-Id`                                                                                                                 |
| Error body                     | pirouter's own errors are `{"error": {"message", "type", "code"}}`, with `code` a string error code; see [Errors](https://pirouter.ai/docs/errors.md). Errors the provider returns are passed on as they are |
| Retries                        | When a provider fails before sending its first byte, pirouter moves to the next one; see [Automatic retry on another provider](https://pirouter.ai/docs/routing.md#automatic-retry-on-another-provider)      |

## Not available today

pirouter does not serve these Gemini methods today:

- `countTokens`. A call to `POST /v1beta/models/{model}:countTokens` returns `400` with the error code `invalid_request` and the `message` `model is required`. The error does not mean your request is missing a field; it means pirouter does not serve that action.
- `embedContent` and the other actions.

## Next steps

- [OpenAI-compatible API](https://pirouter.ai/docs/protocols/openai.md): Call OpenAI-protocol models with the official OpenAI SDK.
- [Authentication & API keys](https://pirouter.ai/docs/authentication.md): Which header carries the key, and how to create and revoke keys.
