> For clean Markdown of any page, append `.md` to the page URL.
> For a complete documentation index, see https://docs.sarvam.ai/llms.txt.
> For full documentation content in one file, see https://docs.sarvam.ai/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.sarvam.ai/_mcp/server.

# Migrating Chat Completion from Gemini to Sarvam

> Endpoint, parameter, and response-format differences between the Gemini API and Sarvam's Chat Completion API, with before/after code for both the OpenAI-compatible endpoint and each platform's native SDK.

This guide covers migrating Gemini text generation and chat calls to Sarvam. It doesn't cover Gemini's audio capabilities; see [Migrating Text-to-Speech](/api-reference-docs/migrations/from-gemini/text-to-speech) and [Migrating Speech-to-Text](/api-reference-docs/migrations/from-gemini/speech-to-text) for those.

**In short:** if your code already goes through the `openai` Python package pointed at Gemini's OpenAI-compatible endpoint, this migration is a two-line change: swap the `api_key` and `base_url`, keep everything else. If you're on the native `google-genai` SDK's `interactions.create()`, `messages` replaces `input` + `system_instruction`, and the response reads back in the same `choices[0].message.content` shape either way.

## Quick start

Base URL and auth header change the same way for every Gemini migration on the native SDK path, so they're folded in here rather than in a separate page:

|                     | Gemini API                                  | Sarvam                            |
| ------------------- | ------------------------------------------- | --------------------------------- |
| Base URL            | `https://generativelanguage.googleapis.com` | `https://api.sarvam.ai`           |
| Auth header         | `x-goog-api-key: <api_key>`                 | `api-subscription-key: <api_key>` |
| Auth failure status | `401`                                       | `403`                             |

With that, replace the generation call:

```python
from google import genai

client = genai.Client(api_key="YOUR_GEMINI_API_KEY")

interaction = client.interactions.create(
    model="gemini-3.5-flash",
    input="What's a good name for a coffee shop in Bengaluru?",
    system_instruction="You are a helpful assistant.",
    generation_config={
        "temperature": 0.7,
        "max_output_tokens": 200,
    },
)

print(interaction.output_text)
```

```javascript
import { GoogleGenAI } from "@google/genai";

const ai = new GoogleGenAI({ apiKey: "YOUR_GEMINI_API_KEY" });

const interaction = await ai.interactions.create({
  model: "gemini-3.5-flash",
  input: "What's a good name for a coffee shop in Bengaluru?",
  system_instruction: "You are a helpful assistant.",
  generation_config: { temperature: 0.7, max_output_tokens: 200 },
});

console.log(interaction.output_text);
```

```bash
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "x-goog-api-key: YOUR_GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-3.5-flash",
    "input": "What'\''s a good name for a coffee shop in Bengaluru?",
    "system_instruction": "You are a helpful assistant."
  }'
```

```python
from sarvamai import SarvamAI

client = SarvamAI(api_subscription_key="YOUR_SARVAM_API_KEY")

response = client.chat.completions(
    model="sarvam-105b",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "What's a good name for a coffee shop in Bengaluru?"},
    ],
    temperature=0.7,
    max_tokens=200,
)

print(response.choices[0].message.content)
```

```javascript
import { SarvamAIClient } from "sarvamai";

const client = new SarvamAIClient({ apiSubscriptionKey: "YOUR_SARVAM_API_KEY" });

const response = await client.chat.completions({
  model: "sarvam-105b",
  messages: [
    { role: "system", content: "You are a helpful assistant." },
    { role: "user", content: "What's a good name for a coffee shop in Bengaluru?" },
  ],
  temperature: 0.7,
  max_tokens: 200,
});

console.log(response.choices[0].message.content);
```

```bash
curl -X POST https://api.sarvam.ai/v1/chat/completions \
  -H "api-subscription-key: YOUR_SARVAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "sarvam-105b",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "What'\''s a good name for a coffee shop in Bengaluru?"}
    ]
  }'
```

If your code goes through the `openai` package instead, see the next section: it's a separate, often simpler route for Chat Completion specifically.

## If you're using the OpenAI-compatible endpoint

Gemini exposes an OpenAI-compatible Chat Completions endpoint, and so does Sarvam. If your code already goes through the `openai` package, migrating is just a client config change:

```python
from openai import OpenAI

client = OpenAI(
    api_key="YOUR_GEMINI_API_KEY",
    base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
)

response = client.chat.completions.create(
    model="gemini-3.5-flash",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello!"},
    ],
)

print(response.choices[0].message.content)
```

```python
from openai import OpenAI

client = OpenAI(
    api_key="YOUR_SARVAM_API_KEY",
    base_url="https://api.sarvam.ai/v1",
)

response = client.chat.completions.create(
    model="sarvam-105b",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello!"},
    ],
)

print(response.choices[0].message.content)
```

Sarvam accepts the same `Authorization: Bearer <key>` header the `openai` package sends by default, on every endpoint, not just Chat Completion. See [Authentication](/api-reference-docs/authentication) for details.

## Endpoint map

The tables below aren't a checklist to apply field-by-field: Quick Start above already shows the whole call swapped over in one go. Use these tables as a reference for what changed and why, not as a sequence of edits to make yourself.

|                        | Gemini API                                                         | Sarvam                                          |
| ---------------------- | ------------------------------------------------------------------ | ----------------------------------------------- |
| Chat / text generation | `POST /v1beta/interactions` (or `/v1beta/openai/chat/completions`) | `POST /v1/chat/completions`                     |
| Streaming              | `POST /v1beta/interactions` with `stream: true`                    | `POST /v1/chat/completions` with `stream: true` |

## Parameter concept map

| Gemini API                                                                                   | Sarvam                                               | Note                                                                                                                 |
| -------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `input` (a string, or a list of prior turns)                                                 | `messages` (a list of `{"role", "content"}` objects) | Same shape the OpenAI-compatible path uses                                                                           |
| `system_instruction` (separate top-level field)                                              | System entry in `messages`                           | Folded into the message list instead of a separate field                                                             |
| `model` (`gemini-3.5-flash`, `gemini-3-pro`, etc.)                                           | `model` (`sarvam-30b` or `sarvam-105b`)              | Two models on cost/latency vs. quality, not a wider family of tiers                                                  |
| `generation_config.temperature`/`top_p`/`max_output_tokens`/`stop_sequences`                 | `temperature`, `top_p`, `max_tokens`, `stop`         | Same concepts and ranges; Sarvam's are top-level call params, not nested under a config object                       |
| `previous_interaction_id` (chain to a stored prior interaction instead of resending history) | Resend the full `messages` list each call            | Matches the widely-used OpenAI Chat Completions convention, so existing message-history logic carries over unchanged |
| `interaction.output_text`                                                                    | `response.choices[0].message.content`                | One direct string vs. the OpenAI Chat Completions shape                                                              |

## What's different by design

* **One message list, not a split input/system\_instruction.** The system prompt is just another entry in `messages`, so the whole conversation, including instructions, lives in one place.
* **No interaction ID to track.** Sarvam follows the stateless, resend-the-history convention used by OpenAI-compatible tooling broadly, rather than Gemini's `previous_interaction_id` chaining; if your app already manages its own message history, it carries over as-is.
* **A response shape you may already know.** `response.choices[0].message.content` matches the OpenAI Chat Completions shape exactly, since Sarvam's endpoint is OpenAI-compatible; there's no separate native shape to learn if you're coming from the OpenAI-compatible Gemini path.
* **Hybrid thinking mode built into the model.** `sarvam-30b` and `sarvam-105b` support both "think" and "non-think" modes via `reasoning_effort`, tuned for complex reasoning and coding without needing a separate reasoning-specific model.
* **Advanced Indic skills are Sarvam's specific strength.** Both models are post-trained on Indian languages and cultural context, with native script and romanized-language support, alongside strong English performance.

## Full example

A small wrapper function, before and after, showing the complete set of changes together:

```python
from google import genai

client = genai.Client(api_key="YOUR_GEMINI_API_KEY")

def ask(prompt: str, system: str = "You are a helpful assistant.") -> str:
    interaction = client.interactions.create(
        model="gemini-3.5-flash",
        input=prompt,
        system_instruction=system,
    )
    return interaction.output_text
```

```javascript
import { GoogleGenAI } from "@google/genai";

const ai = new GoogleGenAI({ apiKey: "YOUR_GEMINI_API_KEY" });

async function ask(prompt, system = "You are a helpful assistant.") {
  const interaction = await ai.interactions.create({
    model: "gemini-3.5-flash",
    input: prompt,
    system_instruction: system,
  });
  return interaction.output_text;
}
```

```bash
ask() {
  local prompt="$1" system="${2:-You are a helpful assistant.}"
  curl -sS -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
    -H "x-goog-api-key: YOUR_GEMINI_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"model\": \"gemini-3.5-flash\", \"input\": \"$prompt\", \"system_instruction\": \"$system\"}"
}
```

```python
from sarvamai import SarvamAI
from sarvamai.core.api_error import ApiError

client = SarvamAI(api_subscription_key="YOUR_SARVAM_API_KEY")

def ask(prompt: str, system: str = "You are a helpful assistant.") -> str:
    try:
        response = client.chat.completions(
            model="sarvam-105b",
            messages=[
                {"role": "system", "content": system},
                {"role": "user", "content": prompt},
            ],
        )
    except ApiError as e:
        if e.status_code == 403:
            raise RuntimeError("Invalid API key. Check your credentials.") from e
        elif e.status_code == 429:
            raise RuntimeError("Rate limit exceeded. Wait and retry.") from e
        elif e.status_code == 503:
            raise RuntimeError("Service overloaded. Retry with backoff.") from e
        raise
    return response.choices[0].message.content
```

```javascript
import { SarvamAIClient, SarvamAIError } from "sarvamai";

const client = new SarvamAIClient({ apiSubscriptionKey: "YOUR_SARVAM_API_KEY" });

async function ask(prompt, system = "You are a helpful assistant.") {
  try {
    const response = await client.chat.completions({
      model: "sarvam-105b",
      messages: [
        { role: "system", content: system },
        { role: "user", content: prompt },
      ],
    });
    return response.choices[0].message.content;
  } catch (e) {
    if (e instanceof SarvamAIError) {
      if (e.statusCode === 403) throw new Error("Invalid API key. Check your credentials.");
      if (e.statusCode === 429) throw new Error("Rate limit exceeded. Wait and retry.");
      if (e.statusCode === 503) throw new Error("Service overloaded. Retry with backoff.");
    }
    throw e;
  }
}
```

```bash
ask() {
  local prompt="$1" system="${2:-You are a helpful assistant.}"
  local body status
  body=$(curl -sS -w '\n%{http_code}' -X POST https://api.sarvam.ai/v1/chat/completions \
    -H "api-subscription-key: YOUR_SARVAM_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"model\": \"sarvam-105b\", \"messages\": [{\"role\": \"system\", \"content\": \"$system\"}, {\"role\": \"user\", \"content\": \"$prompt\"}]}")
  status="${body##*$'\n'}"
  case "$status" in
    403) echo "Invalid API key. Check your credentials." >&2; return 1 ;;
    429) echo "Rate limit exceeded. Wait and retry." >&2; return 1 ;;
    503) echo "Service overloaded. Retry with backoff." >&2; return 1 ;;
  esac
  # Minimal single-value extraction; use a real JSON parser for production code.
  echo "${body%$'\n'*}" | grep -o '"content":"[^"]*"' | tail -1 | cut -d'"' -f4
}
```

## Common mistakes

Sarvam (and the OpenAI-compatible shape generally) expects the system prompt as a `{"role": "system", ...}` entry at the start of `messages`, not a separate top-level field. Fold it into the message list rather than looking for a `system_instruction` parameter.

Sarvam's response doesn't have an `output_text` shortcut. Read the reply from `response.choices[0].message.content`, matching the OpenAI Chat Completions shape.

There isn't one, and that's fine: resend the conversation so far as the `messages` list on every call, the same way OpenAI-compatible tooling generally works. There's no separate interaction resource to reference or manage the lifecycle of.

If you're using the `openai` package against Sarvam's endpoint, pass the key as `api_key` on the `OpenAI(...)` client as usual; don't also try to set the `api-subscription-key` header manually; the two aren't meant to be combined on the same request.

When `reasoning_effort` is used, reasoning tokens count against the same `max_tokens` budget as the visible reply. A limit copied over from a non-reasoning Gemini setup may cut off responses; raise it if you turn reasoning on.

## See also

* [Migration overview](/api-reference-docs/migrations/from-gemini/overview): base URL, auth, and SDK client changes
* [Migrating Text-to-Speech](/api-reference-docs/migrations/from-gemini/text-to-speech)
* [Migrating Speech-to-Text](/api-reference-docs/migrations/from-gemini/speech-to-text)
* [Chat Completion Overview](/api-reference-docs/api-guides-tutorials/chat-completion/overview)
* [Authentication](/api-reference-docs/authentication)
* [Errors & Troubleshooting](/api-reference-docs/errors-troubleshooting)