Gemini API Integration: Two Endpoints, Five Minutes
A copy-paste Gemini API guide: how the OpenAI-compatible endpoint differs from native generateContent, what each unlocks, live pricing and cost math.
The first fork in the road when integrating Gemini is not the API key, it's the endpoint: /v1/chat/completions on one side, /v1beta/models/{model}:generateContent on the other. Both will happily call gemini-3.1-flash-lite-preview, but they don't give you the same capabilities. This post covers the differences, minimal working code for both, real prices and the errors that actually bite.
What the two endpoints really do
The OpenAI-compatible endpoint is a translator. It converts your messages array into Gemini contents, then translates the response back into the OpenAI shape. That translation buys compatibility and costs fidelity. The native endpoint has no translator: what you send is what the model receives.
| Aspect | OpenAI-compatible /v1/chat/completions | Native /v1beta/models/{model}:generateContent |
|---|---|---|
| Auth header | Authorization: Bearer sk-... | x-goog-api-key: sk-... (Bearer also accepted) |
| Message shape | messages: [{role, content}] | contents: [{role, parts:[{text}]}] |
| Roles | system / user / assistant | only user and model; system prompt via systemInstruction |
| Multimodal | image_url entries in content | inlineData / fileData inside parts |
| Streaming | stream: true, SSE | :streamGenerateContent?alt=sse |
| Thinking budget | usually not passed through | generationConfig.thinkingConfig.thinkingBudget |
| Structured output | response_format | generationConfig.responseMimeType |
| Official SDK | openai package + base_url | google-genai + http_options |
Rule of thumb: use the compatible endpoint for tooling, switch to native when you need a Gemini-specific feature. It's the same key on both, so they can coexist.
Step 1: key and endpoints
Create an sk- token in the console, then note two addresses:
- Compatible base URL:
https://api.zaimod.com/v1 - Native:
https://api.zaimod.com/v1beta/models/{model}:generateContent
Full account setup is in the quickstart; let's go straight to code.
Step 2: first request through the compatible endpoint
If you already use the openai package, this is a two-line change:
from openai import OpenAI
client = OpenAI(
api_key="sk-your-zaimod-key",
base_url="https://api.zaimod.com/v1", # the only change
)
resp = client.chat.completions.create(
model="gemini-3.1-flash-lite-preview",
messages=[{"role": "user", "content": "Explain vector search in three sentences."}],
)
print(resp.choices[0].message.content)
print(resp.usage) # prompt_tokens / completion_tokens — keep this for reconciliationNo SDK? Verify the key and network with curl first:
curl https://api.zaimod.com/v1/chat/completions \
-H "Authorization: Bearer $ZAIMOD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.1-flash-lite-preview",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 256
}'A 200 with a usage block means the path works. This single check eliminates most "can't connect" reports, which are almost always a trailing space in the key or a misspelled model name.
Step 3: move to native for the features that matter
Four things the translation layer loses or fuzzes.
System instruction and history
Gemini has no system role. The system prompt lives in systemInstruction, history goes in contents, and role is only user or model:
{
"systemInstruction": {"parts": [{"text": "You are a terse technical editor. Output conclusions and evidence only."}]},
"contents": [
{"role": "user", "parts": [{"text": "Tighten this: the API is slow."}]},
{"role": "model", "parts": [{"text": "Average API latency exceeds two seconds."}]},
{"role": "user", "parts": [{"text": "Shorter."}]}
],
"generationConfig": {"temperature": 0.2, "maxOutputTokens": 512}
}Multimodal: text and files in the same parts array
{
"contents": [{
"role": "user",
"parts": [
{"text": "What is the error in this screenshot?"},
{"inlineData": {"mimeType": "image/png", "data": "<base64>"}}
]
}]
}Images, audio and video all use parts; for URLs use fileData. Video analysis is most complete on the Gemini premium group, but that group carries a ×1.5 multiplier — do the math before pointing a long-video job at it.
Streaming requires alt=sse
curl -N "https://api.zaimod.com/v1beta/models/gemini-3.1-pro-preview:streamGenerateContent?alt=sse" \
-H "x-goog-api-key: $ZAIMOD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contents": [{"parts": [{"text": "Write a 100-word product launch blurb."}]}]}'Drop alt=sse and you get a JSON array instead of a stream. Feed that to an SSE parser and the UI renders nothing — the single most common native-endpoint bug.
Thinking budget and JSON output
{
"contents": [{"parts": [{"text": "Extract party, amount and signing date from this contract."}]}],
"generationConfig": {
"responseMimeType": "application/json",
"thinkingConfig": {"thinkingBudget": 2048}
}
}thinkingBudget drives both reasoning depth and cost. Extraction tasks usually need 1024–2048; only proofs and multi-step math need more. Field-level details are in the Gemini native endpoint docs.
Pricing: model the cost before you ship
Live list prices (USD per 1M tokens, input / output, default group):
| Model | Input | Output | Good for |
|---|---|---|---|
| gemini-3.1-flash-lite-preview | $0.25 | $1.50 | classification, extraction, bulk summaries |
| gemini-2.5-flash | $0.30 | $2.50 | mixed text + image understanding |
| gemini-2.5-pro | $1.80 | $15.30 | long documents, hard reasoning |
| gemini-3.1-pro-preview | $4.00 | $24.00 | flagship long-context work |
| gpt-4o-mini (reference) | $0.156 | $0.624 | ultra-cheap general fallback |
For 2,000 calls a day at 1,500 input and 500 output tokens each, you burn 3M input + 1M output per day:
- flash-lite: 3 × 0.25 + 1 × 1.50 = $2.25/day, roughly $68/month
- 2.5-pro: 3 × 1.80 + 1 × 15.30 = $20.70/day, roughly $621/month
- 3.1-pro: 3 × 4.00 + 1 × 24.00 = $36.00/day, roughly $1,080/month
Same business logic, 30× spread. The fix is boring: ship the whole pipeline on flash-lite, route only the requests it fails to pro. Live numbers are on the pricing page — don't memorise the table, prices move.
The four errors you will hit
| Error | Real cause | Fix |
|---|---|---|
401 invalid api key | trailing whitespace, or a key from another platform | recopy, watch for newlines |
404 model not found | misspelled model, or not available in your group | use exact names from the model list |
400 contents is not specified | sent messages to the native endpoint | switch to contents + parts |
| stream arrives as one blob | ?alt=sse missing | add it, watch with curl -N |
429 and "insufficient balance" look similar but need different handling — backoff versus top-up. The decision table lives in billing and error codes.
Which endpoint should you pick
- Cursor, Cline, Cherry Studio, LangChain: compatible endpoint — they only speak OpenAI
- Video understanding,
thinkingBudget,responseMimeType: native - Multi-vendor (GPT + Claude + Gemini on one key): compatible by default, native by exception
- Large existing OpenAI codebase: compatible, one base_url change
Where the compatibility layer ends is covered in more depth in the OpenAI-compatible protocol explainer.
Closing
The hard part of a Gemini integration isn't auth, it's knowing which layer you're talking to. The compatible endpoint buys migration speed; the native endpoint buys capability ceiling. Ship on the first, replace the calls that genuinely need multimodality and thinking budgets with the second, and you get both without a rewrite.
New accounts come with trial credit, so both endpoints are testable in a few minutes; after that it's pay-as-you-go with balances that don't expire. Pick two candidate models off the pricing page and A/B them against your own traffic.
Further reading
- Gemini native endpoint: full generateContent reference
- Quickstart: your first request in five minutes
- Five signals an API gateway is about to fold