> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Native Gemini protocol

> Call Gemini using model paths, native contents, and candidates.

This guide calls Gemini's native `generateContent` protocol. Confirm native Gemini support for your channel and model. The example sends a gateway token issued by your deployment in the `x-goog-api-key` header.

## Address and request structure

| Item | Example convention |
| - | - |
| Request | `POST /v1beta/models/{model}:generateContent` |
| Root URL | `API_ROOT_URL` excludes `/v1` and `/v1beta`; deployment prefixes are allowed |
| Model | In the path, not the body; use an administrator-confirmed bare model ID |
| Instructions and input | `systemInstruction`; `contents[].parts` |
| Conversation roles | `user` / `model`, not `assistant` |
| Output | `candidates[].content.parts`; check `finishReason` and `promptFeedback` |

The project selects a channel from the model path. The header authenticates the gateway token; the channel adapter supplies upstream authentication. The project also accepts the `key` query parameter, but this example keeps credentials out of URLs by using a header.

## Setup

Use Node.js 22.18+ or 24 with built-in `fetch`; no Google SDK is required. Set `API_KEY` in your environment, then configure the root URL and actual model:

```sh theme={null}
export API_ROOT_URL="https://gateway.example.com"
export API_MODEL="YOUR_MODEL_ID"
# API_KEY must already be set in the process environment.
```

Save as `native-gemini.mts` and run `node native-gemini.mts`; from the documentation directory, run `node examples/native-gemini.mts`. This example accepts only letters, digits, dots, underscores, and hyphens in a model ID. It rejects `models/` prefixes, paths, and action suffixes. This is a local URL validation constraint, not the gateway's full model naming contract.

## TypeScript

Complete example:

```typescript theme={null}
const { API_ROOT_URL, API_KEY, API_MODEL } = process.env;
if (!API_ROOT_URL || !API_KEY || !API_MODEL) {
  throw new Error('Set API_ROOT_URL, API_KEY, and API_MODEL first.');
}
// This example accepts a bare model ID, without models/ or an action suffix.
if (!/^[A-Za-z0-9._-]+$/.test(API_MODEL) || ['.', '..'].includes(API_MODEL)) {
  throw new Error('Use a bare model ID containing letters, digits, dots, underscores, or hyphens.');
}

try {
  const response = await fetch(`${API_ROOT_URL.replace(/\/+$/, '')}/v1beta/models/${encodeURIComponent(API_MODEL)}:generateContent`, {
    method: 'POST', redirect: 'error', signal: AbortSignal.timeout(60_000),
    headers: { 'Content-Type': 'application/json', 'x-goog-api-key': API_KEY },
    body: JSON.stringify({
      systemInstruction: { parts: [{ text: 'Answer briefly in plain text.' }] },
      contents: [{ role: 'user', parts: [{ text: 'Say hello in one short sentence.' }] }],
    }),
  });
  if (!response.ok) {
    console.error(`Request failed (HTTP ${response.status}). No automatic retry.`);
    process.exitCode = 1;
  } else {
    const result = await response.json();
    if (result.error || result.promptFeedback?.blockReason
      || !Array.isArray(result.candidates) || result.candidates.length !== 1) {
      throw new Error('Blocked or missing candidate.');
    }
    const candidate = result.candidates[0];
    if (candidate.finishReason !== 'STOP' || candidate.content?.role !== 'model'
      || !Array.isArray(candidate.content.parts) || candidate.content.parts.length === 0) {
      throw new Error('No complete text response.');
    }
    const text = candidate.content.parts.map((part: Record<string, unknown>) => {
      if (!part || typeof part.text !== 'string' || Object.keys(part).some(
        (key) => !['text', 'thought', 'thoughtSignature'].includes(key),
      ) || (part.thought !== undefined && typeof part.thought !== 'boolean')) {
        throw new Error('Unsupported content part.');
      }
      return part.thought === true ? '' : part.text;
    }).join('');
    if (!text.trim()) throw new Error('Empty visible text.');
    console.log(text);
  }
} catch {
  console.error('Gemini request failed or did not produce complete plain text. No automatic retry.');
  process.exitCode = 1;
}
export {};
```

## Read the result

The example requires exactly one candidate, `finishReason: STOP`, `role: model`, and nonempty visible text. A `promptFeedback.blockReason`, `MAX_TOKENS`, `SAFETY`, an error object, or tool/media parts fails the example. Text marked `thought: true` is omitted from display; thoughts without a visible answer also fail. Success prints the joined answer. Failure prints no partial answer, follows no redirects, and triggers no automatic retry.

Native tools use `tools[].functionDeclarations`, `functionCall`, and `functionResponse`, which differ from Chat tool messages. For multiple turns, preserve the whole model content, including returned `thoughtSignature` values and tool identifiers, before adding new user content. Hiding thoughts for display does not justify dropping signatures during replay. This example is a single-turn text client, not a native tool loop.

## Streaming path and support boundaries

Native SSE uses `POST /v1beta/models/{model}:streamGenerateContent?alt=sse`. Adding `stream: true` to a `generateContent` body does not switch actions. Each SSE `data` contains a Gemini response object, not a Chat delta or a named Responses event. Handle parts by candidate index and check the final `finishReason`; neither HTTP success nor EOF proves normal completion. A network chunk can split a UTF-8 character or an SSE event.

This runnable example covers `generateContent` text responses. [Native SSE and tool loops](/en/guides/native-gemini-streaming) provides a complete Python streaming implementation. Image/audio extensions and actual channels still need separate implementation and integration testing. Do not reuse the event parsers from [Chat / Responses streaming tools](/en/guides/streaming-tools) for Gemini.

The project does not implement `/v1beta/...:countTokens` and currently returns 404; do not rely on provider SDK counting methods. Verify model routing, optional fields, and conversion behavior against your deployment.

[Gemini generateContent reference](/en/api-reference/gemini-generate) · [Error handling](/en/guides/errors)

Protocol reference: [Google generateContent / streamGenerateContent](https://ai.google.dev/api/generate-content). Gateway endpoints and authentication follow this repository's routing and adapters.

[阅读中文](/zh/guides/native-gemini)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.