Gemini API in Python: requests via the OpenAI SDK and OpenAI format

A working Gemini API example in Python with no extra library: change base_url in the OpenAI SDK, add streaming and JSON, and compare with google-genai.

5 min · · development · API keys

If you are looking for how to call the Gemini API from Python, you have two options: Google's native library, or the familiar OpenAI SDK pointed at a different address. This article covers the second option with working code: a client, a plain request, streaming, JSON output, error handling and a cost estimate. At the end we compare it with the native google-genai and say when each one fits.

All examples go through the Zukko gateway: one fetch_... key, the OpenAI format and the gemini-3.8-flash model. Prices are as of October 18, 2026.

Why call Gemini through the OpenAI SDK

The main benefit is that your code is not tied to one vendor. The OpenAI(...) client only knows three things: an address, a key and a model name. To move from Gemini to GPT or Claude you change the model string. You do not rewrite response parsing, streaming or error handling.

That helps in three situations:

  • the project already uses openai and you want to add a cheap model for bulk tasks;
  • you are comparing several models on your own data and do not want to write an adapter for each;
  • a tool (LangChain, LlamaIndex, your own service) only speaks the OpenAI-compatible API.

Google itself supports this mode: the Gemini API documentation has a section on OpenAI library compatibility where you change three lines (key, base URL and model). The same page says that if you do not use OpenAI libraries, calling the Gemini API directly is simpler. We come back to that in the comparison section.

Installation and the first request

bash
pip install openai

Create a key on the API keys page and keep it in an environment variable rather than in code:

bash
export OPENAI_BASE_URL="https://api.zukko.pro/v1"
export OPENAI_API_KEY="fetch_your_key"

A minimal request:

bash
from openai import OpenAI

client = OpenAI()  # reads OPENAI_BASE_URL and OPENAI_API_KEY

MODEL = "gemini-3.8-flash"

resp = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "Answer briefly and to the point."},
        {"role": "user", "content": "How is a list different from a tuple in Python?"},
    ],
)
print(resp.choices[0].message.content)
print(resp.usage)

If you prefer to set the address explicitly instead of using environment variables:

bash
client = OpenAI(base_url="https://api.zukko.pro/v1", api_key="fetch_your_key")

The address ends with /v1 because the OpenAI SDK only appends /chat/completions itself. For more on the client see the Python section of the API reference, and the chat completions page next to it covers the endpoint itself.

Streaming

For interfaces and chat bots it is convenient to receive the answer in pieces:

bash
stream = client.chat.completions.create(
    model=MODEL,
    messages=[{"role": "user", "content": "Tell me about generators in Python"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices:
        piece = chunk.choices[0].delta.content
        if piece:
            print(piece, end="", flush=True)
    elif chunk.usage:
        print(f"\n[in: {chunk.usage.prompt_tokens}, out: {chunk.usage.completion_tokens}]")

The if chunk.choices: check is required. At the end of the stream the server sends a final chunk with usage and an empty choices list, and accessing chunk.choices[0] without the check raises IndexError.

JSON instead of free text

If a program has to parse the answer, ask for JSON explicitly and still validate the result:

bash
import json

resp = client.chat.completions.create(
    model=MODEL,
    messages=[
        {
            "role": "system",
            "content": "Return only a JSON object with keys name, date, participants (array of strings).",
        },
        {"role": "user", "content": "Anna and Boris are going to an AI conference on Friday."},
    ],
    response_format={"type": "json_object"},
)

try:
    data = json.loads(resp.choices[0].message.content)
except json.JSONDecodeError:
    data = None  # retry the request or send it to manual review
print(data)

The response_format parameter belongs to the OpenAI format. Google's compatibility documentation shows schema-based structured output examples, but how exactly the parameter reaches the model through a gateway is worth testing on your own request. If you get a 400 error, drop response_format and keep only the prompt instruction plus json.loads: for simple schemas that is usually enough.

Errors and retries

bash
from openai import OpenAI, APIStatusError, APIConnectionError

try:
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": "Hello!"}],
    )
except APIStatusError as e:
    if e.status_code == 401:
        print("Invalid key")
    elif e.status_code == 402:
        print("Insufficient balance")
    elif e.status_code == 404:
        print("Model is not in the catalog, check GET /v1/models")
    elif e.status_code in (429, 503):
        print("Key limit or provider unavailable, retry later")
    else:
        raise
except APIConnectionError:
    print("Network unavailable")

Retrying makes sense for 429 and 503, with a growing pause. Errors 401, 402 and 403 are not fixed by retries. If a model is switched off, the server returns 503 with the code model_disabled: pick another model from GET /v1/models. The model catalog can always be fetched with client.models.list().

What it costs

Zukko prices as of October 18, 2026 for gemini-3.8-flash: $0.125 per 1M input tokens and $0.6 per 1M output tokens. Context is 1M tokens, and only text is accepted as input.

An example with illustrative volumes: 1000 requests of 2000 input and 500 output tokens each.

  • input: 1000 × 2000 = 2M tokens, 2 × 0.125 = $0.25;
  • output: 1000 × 500 = 0.5M tokens, 0.5 × 0.6 = $0.30;
  • total: 0.25 + 0.30 = $0.55.

At the official prices from our table (input $0.75, output $3.75) the same volume would cost 2 × 0.75 + 0.5 × 3.75 = $3.375. The exact amount for each request is in the X-Zukko-Cost-Micros header (in micro-dollars) via with_raw_response:

bash
raw = client.chat.completions.with_raw_response.create(
    model=MODEL,
    messages=[{"role": "user", "content": "Hello!"}],
)
print(raw.headers.get("x-zukko-cost-micros"))
resp = raw.parse()

The header is only present on regular (non-streaming) responses. The current list of models and prices is GET https://api.zukko.pro/v1/models or the Models page in the dashboard. If the model reasons before answering, those tokens count toward output, so on hard prompts look at usage rather than the length of the visible answer.

Native google-genai or the OpenAI SDK

CriterionOpenAI SDK via gatewayNative google-genai
Installpip install openaipip install google-genai
Request shapemessages, chat.completionsinput, interactions.create
Switching vendorschange the model stringrewrite the code
Gemini specificsvia compatibility-layer parametersfull Google feature set
Keygateway fetch_... keyGoogle AI Studio key

The native client looks like this (per Google's current quickstart, the key is read from GEMINI_API_KEY):

bash
from google import genai

client = genai.Client()
interaction = client.interactions.create(
    model="gemini-3.8-flash",
    input="How is a list different from a tuple in Python?",
)
print(interaction.output_text)

Choose the native library if you need features missing from the compatibility layer, such as full thinking controls and anything Google ships before others. Choose the OpenAI SDK if portable code, one format for many models and a shared balance matter more. One caveat on parameters: Google's troubleshooting page says newer models in the line (3.6 and later) ignore custom temperature and top_p values, so do not count on them as a tuning knob.

Try it on your own data: create a key in the dashboard, drop it into the example above and compare gemini-3.8-flash answers with gpt-6.1-sol or deepseek-v4.1-flash by changing one line. For choosing between models by price and strength index see the model strength index article, and for Gemini as a standalone model see Gemini 3.8 Flash via API.

FAQ

Do I need a separate Google key to call Gemini through the OpenAI SDK?

Not through the Zukko gateway: a fetch_... key is enough. Going directly to Google requires a key from Google AI Studio, and availability depends on your region.

Which base_url should I use?

https://api.zukko.pro/v1, with the /v1 suffix. The Anthropic SDK uses a different address (without /v1), but that does not apply to Gemini.

Do streaming and function calling work?

Streaming works in the OpenAI format, see the example above. For function calling and other compatibility-layer features, follow Google's documentation and test the behavior on your own task.

How do I find the exact model name?

Send GET https://api.zukko.pro/v1/models with your key or open the Models page in the dashboard. An unknown name returns a 404 model_not_found error.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

Gemini API Python: calls via the OpenAI SDK · Zukko