Convert Google Gemini's web interface into an OpenAI-compatible API. Zero cost, cross-platform, single file.
- Optional API Keys: no auth when
api_keysis empty, OpenAI-style Bearer auth when configured - OpenAI Compatible: Drop-in replacement for
/v1/chat/completionsand/v1/models - Tool Calling: Full function calling support (OpenAI format)
- Multiple Models: Flash (3.6), Extended Thinking (20k+ char output), Pro, Auto, Lite
- Thinking Depth: Adjustable via
@think=Nsuffix (0=deepest, 4=shallowest) - Web Search: Built-in internet access (Gemini's native search)
- Cross-Platform: Pure Python, single optional dependency (
httpxfor streaming) - Streaming: SSE streaming support via
httpx - Codex CLI: Responses API (
/v1/responses) for OpenAI Codex integration - Gemini CLI: Google native API (
/v1beta/models) for Gemini CLI compatibility
pip install httpx
python gemini_web2api.pyServer starts at http://localhost:8081/v1.
You can run two instances side by side, each on its own port: one anonymous, one with a cookie.
Create two config files (see .env.example),
.env.anon (anonymous):
PORT=8081
.env.cookie (with cookie):
PORT=8082
COOKIE_FILE=cookie.txt
Start both ports with one command:
python start_all.py| Port | Config | Routing behavior |
|---|---|---|
| 8081 | .env.anon |
Anonymous, all models route to Flash-Lite |
| 8082 | .env.cookie |
Cookie auth, model categories take effect |
Point clients at 8081 for anonymous, 8082 for authenticated — the two don't interfere. The startup banner prints Env file: and Cookie: yes/none (anonymous) so you can tell instances apart.
Or start them separately:
python -m gemini_web2api --env-file .env.anon
python -m gemini_web2api --env-file .env.cookieNote: if the file
COOKIE_FILEpoints to does not exist, the instance silently runs in anonymous mode without erroring. See Cookie configuration below.
| Field | Value |
|---|---|
| Base URL | http://localhost:8081/v1 |
| API Key | any api_keys value from config.json; anything if not configured |
| Model | gemini-3.5-flash-thinking |
curl http://localhost:8081/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-your-key" \
-d '{"model":"gemini-3.5-flash","messages":[{"role":"user","content":"Hello!"}]}'curl.exe --% http://127.0.0.1:8081/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer sk-your-key" -d "{\"model\":\"gemini-3.5-flash\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello!\"}]}"Note: On Windows PowerShell, use
curl.exeand--%so PowerShell does not reinterpret JSON quoting or curl options.
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8081/v1", api_key="sk-your-key")
resp = client.chat.completions.create(
model="gemini-3.5-flash-thinking",
messages=[{"role": "user", "content": "Explain quantum computing"}]
)
print(resp.choices[0].message.content)export GEMINI_API_KEY=none
export GOOGLE_GEMINI_BASE_URL=http://localhost:8081
geminiSupports Google native API endpoints:
GET /v1beta/models— list modelsPOST /v1beta/models/{model}:generateContent— non-streamingPOST /v1beta/models/{model}:streamGenerateContent— streaming (SSE)
| Model | Description | Output |
|---|---|---|
gemini-3.8-flash |
All-around model (latest) | ~12k chars |
gemini-3.6-flash |
All-around model | ~12k chars |
gemini-3.5-flash |
Alias for gemini-3.6-flash | ~12k chars |
gemini-3.5-flash-thinking |
Extended thinking, longest output | ~20k chars |
gemini-3.5-flash-thinking-lite |
Adaptive thinking depth | ~15k chars |
gemini-3.1-pro |
Advanced math & code (needs cookie) | ~12k chars |
gemini-auto |
Auto model selection | varies |
gemini-flash-lite |
Fastest answers, lightweight | ~10k chars |
Append @think=N to any model name:
gemini-3.5-flash-thinking@think=0 # deepest (default)
gemini-3.5-flash-thinking@think=2 # medium
gemini-3.5-flash-thinking@think=4 # shallowest
Model selection sends a "model category" to the Gemini backend via the [79] field of the request payload, but for anonymous requests the backend ignores the category and routes every model to Flash-Lite (verified by testing). With a cookie, the category is honored:
gemini-3.8-flash/gemini-3.6-flash(FAST category) → routes to whatever the account's FAST tier currently servesgemini-3.1-pro(PRO category) → free Google accounts silently fall back to Flash; Gemini Advanced (paid subscription) is required for real Pro routing- There is no official unauthenticated API: the free Gemini API tier requires a Google account + AI Studio key; anonymous access is only the web chat's basic tier
The extension exports cookie + SAPISID + XSRF token + gemini_bl in one go:
-
Open
chrome://extensionsin Chrome → enable Developer mode → Load unpacked → select thegemini-cookie-sync-extensiondirectory from this repo -
Open gemini.google.com/app, sign in to your Google account, refresh the page
-
Click the extension icon → Inspect session, and confirm it shows:
XSRF / SNlM0e: present gemini_bl / cfb2h: present -
Click Export gemini-auth.json — you get a complete auth file with cookie,
sapisid,xsrf_token,gemini_bl, andauth_user -
Set
"cookie_file": "gemini-auth.json"inconfig.json
The
blversion of gemini.google.com changes with deployments; the exportedgemini_blis the current value and more reliable than hand-filling.
-
Open gemini.google.com, sign in, press F12 to open DevTools
-
Application tab → Cookies in the sidebar →
https://gemini.google.com -
Copy these cookies:
Cookie Purpose SID/HSID/SSIDGoogle sign-in state APISID/SAPISIDAPI auth (SAPISID is also used for sapisidhash) __Secure-1PSIDSession credential -
Create
cookie.txtin the project root, JSON format:
{"cookie": "SID=xxx; HSID=xxx; SSID=xxx; APISID=xxx; SAPISID=xxx; __Secure-1PSID=xxx", "sapisid": "your SAPISID value"}Or the plain single-line format:
SID=xxx; HSID=xxx; SSID=xxx; APISID=xxx; SAPISID=xxx; __Secure-1PSID=xxx
Start with:
python gemini_web2api.py --cookie-file cookie.txtIf the signed-in Gemini page URL contains an account index, such as:
https://gemini.google.com/u/1/app/...
set auth_user to that index. Authenticated web requests may also require the page XSRF token. In the rendered Gemini page source, this token is exposed as SNlM0e; pass it as xsrf_token in config.json. The server sends it as the at form field.
Example:
{
"cookie_file": "/app/cookie.txt",
"auth_user": "1",
"xsrf_token": "AOOh0P...",
"gemini_bl": "boq_assistant-bard-web-server_YYYYMMDD.xx_p0"
}If authenticated requests return HTTP 400 with an xsrf error, refresh Gemini Web, update xsrf_token, and make sure auth_user matches the /u/<index>/ part of the browser URL.
Pro routing requires Gemini Advanced (paid subscription). A free Google account cookie will authenticate but silently fall back to Flash.
After starting the cookie instance, send a request and check the server log:
curl http://localhost:8082/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-your-key" \
-d '{"model":"gemini-3.8-flash","messages":[{"role":"user","content":"hi"}]}'The server log prints the model name the backend actually routed to:
[xx:xx:xx] actual model from response: 3.X Flash
That is the model really served by the FAST tier. If it still shows 3.5 Flash-Lite, check the cookie file path and whether the session is still valid.
Create config.json in the same directory:
{
"port": 8081,
"host": "0.0.0.0",
"retry_attempts": 3,
"retry_delay_sec": 2,
"request_timeout_sec": 180,
"gemini_bl": "boq_assistant-bard-web-server_20260716.08_p0",
"auth_user": null,
"xsrf_token": null,
"api_keys": ["sk-your-key"],
"cookie_file": null,
"proxy": null,
"log_requests": true,
"temporary_chats": false
}Set temporary_chats to true to use Gemini Web temporary chats instead of
persisting conversations to the account history.
When api_keys is [], authentication is disabled. When one or more keys are set, /v1/* endpoints require Authorization: Bearer <key> or x-api-key: <key>.
The proxy provides local SQLite-backed context caching. Create a cache with
POST /v1/caches, then pass its cache_id in Chat Completions or Responses
requests. With auto_cache enabled, stable system context and prior turns in
multi-turn requests are cached automatically. Cache usage is reported as the
estimated usage.prompt_tokens_details.cached_tokens field; this is proxy-side
accounting and is not an upstream Gemini billing value.
cp config.example.json config.json
docker build -t gemini-web2api .
docker run -d --name gemini-web2api -p 8081:8081 -v ./config.json:/app/config.json gemini-web2apiOr use Docker Compose:
cp config.example.json config.json
docker compose up -dTo mount a cookie file:
docker run -d --name gemini-web2api -p 8081:8081 -v ./config.json:/app/config.json -v ./cookie.txt:/app/cookie.txt gemini-web2apiSet "cookie_file": "/app/cookie.txt" in config.json.
Note: If you get empty responses (
content: null) with Docker's default bridge network, switch to host networking:docker run --network host ...or addnetwork_mode: hostin your compose file. This is caused by Gemini's upstream rejecting requests from certain Docker NAT IP ranges.
If you cannot access gemini.google.com directly (connection timeout), configure a proxy:
Method 1: CLI argument
python gemini_web2api.py --proxy http://127.0.0.1:7890Method 2: config.json
{"proxy": "http://127.0.0.1:7890"}Method 3: Environment variable (auto-detected)
export HTTPS_PROXY=http://127.0.0.1:7890
python gemini_web2api.pyWorks with Clash, V2Ray, Shadowsocks, or any HTTP proxy.
resp = client.chat.completions.create(
model="gemini-3.5-flash",
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather for a city",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
}
}]
)OpenAI-style multimodal messages are supported for Chat Completions and the Responses API. Use either HTTP(S) image URLs or base64 data URLs:
resp = client.chat.completions.create(
model="gemini-3.6-flash",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "Describe this image"},
{"type": "image_url", "image_url": {"url": "https://example.com/image.png"}}
]
}]
)- Image upload may require cookies: Multimodal input uses Gemini Web's image upload endpoint. If anonymous upload fails, configure a Gemini cookie.
- Not real Pro/Ultra: Without a paid subscription cookie,
gemini-3.1-proroutes to the same Flash model. The "Pro" label is a UI preference, not a backend model switch. - Single-turn only: Each request is an independent conversation. Multi-turn context is simulated by including previous messages in the prompt.
- Rate limits: Google may throttle high-frequency requests. The server retries automatically but sustained heavy use may be blocked.
- Python 3.8+
httpx(pip install httpx) — used for streaming requests- Network access to
gemini.google.com(proxy/VPN may be needed in some regions)
This tool reverse-engineers Google Gemini's web StreamGenerate protocol. It sends requests to the same endpoint that the Gemini web app uses, converting between OpenAI's API format and Gemini's internal protobuf-like format.
The model selection is controlled by field [79] in the request payload, mapped from Gemini's frontend JavaScript source (MODE_CATEGORY enum).
- Inspired by the open-source API proxy ecosystem
MIT
本项目的开发 agent 能力由 GenericAgent 提供。
