8000 GitHub - ntthanh2603/gemini-web-to-api: ✨Reverse-engineered API for Gemini web app. It can be used as a genuine API key from OpenAI, Gemini, and Claude. · GitHub
[go: up one dir, main page]

Skip to content

About

✨Reverse-engineered API for Gemini web app. It can be used as a genuine API key from OpenAI, Gemini, and Claude.

Topics

Resources

Stars

636 stars

Watchers

7 watching

Forks

Repository files navigation

Gemini Logo

Release Go Version Docker GHCR License Maintained

Stars Issues PRs Welcome

Gemini Web To API 🚀

Transforms Google Gemini web interface into a standardized REST API.
Access Gemini's power without API keys — just use your cookies!

Created with ❤️ by @ntthanh2603

Note

This project is intended for research and educational purposes only. Please use responsibly and refrain from any commercial use.

Warning

This project is not affiliated with Google. It uses reverse-engineered web cookies and may not comply with Google's Terms of Service. Use at your own risk — the author assumes no responsibility for any account actions or data loss.


🎯 Why Gemini Web To API?

Problem: You want to use Google Gemini's latest models, but you don't have an API key or prefer not to use one.

Solution: Creates a local API server that:

  • ✅ Connects to Gemini's web interface using your browser cookies
  • ✅ Exposes OpenAI / Claude / Gemini-compatible API endpoints
  • ✅ No API keys needed — just cookies from your browser
  • ✅ Handles authentication and session management automatically

Use Cases:

  • Use Gemini without API keys
  • Test Gemini integration locally
  • Build applications leveraging Gemini's latest models
  • Develop with cookie-based authentication

⚡ Quick Start

🐳 Option A — Docker Run (no setup required)

No cloning needed — pull and run directly from the registry.

Step 1 — Get your cookies

Warning

Keep these values secure and never share or commit them — they provide direct access to your Google account.

  1. Go to gemini.google.com and sign in
  2. Press F12 → Network, reload Gemini, and select a batchexecute?rpcids=otAQ7b request
  3. Copy its complete Cookie request header. If the request URL contains an account slot such as /u/2/, also use GEMINI_AUTH_USER="2" below.

Step 2 — Run

docker run -d -p 4981:4981 \
  -e GEMINI_COOKIES="your_complete_cookie_header" \
  -e GEMINI_AUTH_USER="2" \
  -e GEMINI_REFRESH_INTERVAL=1440 \
  -e GEMINI_MAX_RETRIES=3 \
  -e GEMINI_TEMPORARY=false \
  -e APP_ENV=production \
  -e RATE_LIMIT_ENABLED=true \
  -e RATE_LIMIT_WINDOW_MS=60000 \
  -e RATE_LIMIT_MAX_REQUESTS=10 \
  -v ./cookies:/home/appuser/.cookies \
  --tmpfs /tmp:rw,size=512m \
  --tmpfs /home/appuser/.cache:rw,size=256m \
  --name gemini-web-to-api \
  --restart unless-stopped \
  ghcr.io/ntthanh2603/gemini-web-to-api:latest

Done! Jump to Test it. 🎉


🛠️ Option B — Build from source

Use this if you want to build for a specific architecture (amd64, arm64, etc.) or modify the source code.

Step 1 — Clone the repository

git clone https://github.com/ntthanh2603/gemini-web-to-api.git
cd gemini-web-to-api

Step 2 — Get your cookies and configure .env

Warning

Keep these values secure and never commit your .env file — it contains credentials that provide access to your Google account.

  1. Go to gemini.google.com and sign in

  2. Press F12 → Network, reload Gemini, and select a batchexecute?rpcids=otAQ7b request

  3. Copy its complete Cookie request header into GEMINI_COOKIES.

  4. Match GEMINI_AUTH_USER to the request URL. For example, use 2 when the URL contains /u/2/; leave it empty when there is no /u/<number>/. Always take the cookie and account slot from the same browser tab.

  5. Create your .env from the example:

    cp .env.example .env
  6. Paste the values into .env:

    GEMINI_COOKIES=your_complete_cookie_header
    GEMINI_AUTH_USER=2
    GEMINI_REFRESH_INTERVAL=1440
    GEMINI_MAX_RETRIES=3
    GEMINI_TEMPORARY=false
    APP_ENV=production
    RATE_LIMIT_ENABLED=true
    RATE_LIMIT_WINDOW_MS=60000
    RATE_LIMIT_MAX_REQUESTS=10

Step 3 — Run

Pick whichever method suits your setup:

Method Command Requirements
🐳 Docker Compose docker compose up -d --build Docker
🐹 Go direct go run cmd/server/main.go Go 1.21+
⚡ Task (dev mode) task dev Task + Air (task install-air)

task dev runs the server with hot reload: Air rebuilds and restarts it whenever a .go, .json or .env file changes (config in .air.toml).

Done! Jump to Test it. 🎉


✅ Test it

curl
8000
 -X POST http://localhost:4981/openai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "gemini-auto", "messages": [{"role": "user", "content": "Hello!"}]}'

Your Gemini Web To API is running at http://localhost:4981 🎉


✨ Features

  • 🌉 Universal AI Bridge: One server, three protocols (OpenAI, Claude, Gemini)
  • 🔌 Drop-in Replacement: Works with existing OpenAI / Claude / Gemini SDKs
  • 🔄 Smart Session Management: Auto-rotates cookies to keep sessions alive
  • ⚡ High Performance: Built with Go and Fiber for speed
  • 🐳 Production Ready: Docker Compose support, Scalar UI, health checks
  • 📝 Well Documented: Interactive API docs at /docs
  • 🖼️ Image Generation: OpenAI-compatible text-to-image with authenticated b64_json output
  • 🧩 Image Inputs: Remote URLs, data: URLs and multiple reference images in chat requests
  • 🎬 Video Generation: Text-to-video (16:9 or 9:16 MP4) via Gemini Web's Videos tool, usable from the OpenAI (client.videos) and Google GenAI (generate_videos) SDKs
  • 🧾 Canvas: Gemini Canvas code files and documents via any chat API — just use a -canvas model (e.g. gemini-auto-canvas)
  • 🎵 Music Generation: Gemini Web's music tool through client.audio.speech (OpenAI) and -music models (Gemini)
  • 🔢 Token usage estimates: Chat endpoints report plausible usage counts, streaming included, even though Gemini Web usually sends none — see Token usage reporting
  • 🧹 Clean Output: Web-UI citation artifacts ([cite: N], [cite: N, M], [cite_start] and ?code_reference / ?code_stdout code-fence suffixes) are stripped from response text on every endpoint
  • 🧱 JSON Responses: OpenAI response_format (json_object / json_schema) is asked for in the prompt, fenced or prose-wrapped replies are cleaned, and one repair attempt is made before falling back to the raw text

See Image generation and image inputs for tested examples, limitations and security guidance. See Video generation for the video APIs and the OpenAI / Gemini SDK examples. See Canvas and the OpenAI / Gemini / Claude SDK examples for canvas mode. See Music generation and the OpenAI / Gemini SDK examples.

Token usage reporting

Gemini Web's wire format usually carries no token counts, so the chat endpoints report estimates: usage on OpenAI and Claude, usageMetadata on Gemini. They come from a rune-based, per-script heuristic and are meant for context tracking and relative load monitoring — they are not billing-grade. Output counts include reasoning text. When a response does carry usageMetadata, its counts are used instead, and any count it leaves out falls back to the estimate.

  • OpenAI: streams end with a usage chunk when the request sets stream_options: {"include_usage": true}.
  • Claude: streams carry usage in message_start and message_delta; /messages/count_tokens uses the same estimate as usage.input_tokens.
  • Gemini: the last streamGenerateContent chunk carries usageMetadata.

Image, video and music generation report no token counts.


🛠️ Configuration

Environment Variables

Variable Required Default Description
GEMINI_COOKIES ✅ Yes — Complete Cookie request header copied from the Gemini Web tab
GEMINI_AUTH_USER ❌ No — Google account slot from the Gemini URL, e.g. 2 for /u/2/app
GEMINI_REFRESH_INTERVAL ❌ No 1440 Cookie rotation interval in minutes (minimum 10), see below
GEMINI_MAX_RETRIES ❌ No 3 Max retry attempts when an API call fails
GEMINI_TEMPORARY ❌ No false Enable stateless/incognito mode for all requests
GEMINI_VIDEO_TIMEOUT_MINUTES ❌ No 10 Minutes a video job may take (maximum 60); only one video job runs at a time
GEMINI_COOKIE_API_KEY ❌ No — Bearer token that enables POST /admin/cookies, see below
PORT ❌ No 4981 Server port
RATE_LIMIT_ENABLED ❌ No false Enable or disable rate limiting
RATE_LIMIT_WINDOW_MS ❌ No 60000 Rate limit time window in milliseconds
RATE_LIMIT_MAX_REQUESTS ❌ No 10 Maximum number of requests allowed per time window

Avoiding Gemini blocks (GEMINI_REFRESH_INTERVAL)

The server periodically rotates the __Secure-1PSIDTS cookie and refreshes the Gemini session token to keep it alive. Doing this too often looks automated, and Google may answer with 429, "unusual traffic" pages or force you to sign in again.

  • The default is 1440 minutes (24h). Values below 10 are raised to 10; 0 or negative values fall back to the default.
  • If you still get blocked, keep the default (or go higher) and also keep RATE_LIMIT_ENABLED=true with a modest RATE_LIMIT_MAX_REQUESTS.
  • If the session starts expiring between rotations (logs show marking client unhealthy), lower it again or copy a fresh cookie header.

Updating cookies from a browser extension (GEMINI_COOKIE_API_KEY)

Set GEMINI_COOKIE_API_KEY to a long random value to let a browser extension push a fresh session instead of restarting the container. When the variable is unset the routes are not registered at all (they answer 404), so existing deployments are unaffected.

curl -X POST http://localhost:4981/admin/cookies \
  -H "Authorization: Bearer $GEMINI_COOKIE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cookies": "SID=...; __Secure-1PSID=...; __Secure-1PSIDTS=..."}'
  • cookies must be the complete Cookie request header, exactly like GEMINI_COOKIES, and must contain both __Secure-1PSID and __Secure-1PSIDTS.
  • The pushed session is verified against Google before it replaces the live one; a bad header returns 4xx and leaves the current session untouched. The token is compared in constant time.
  • Pushing the __Secure-1PSIDTS of a healthy live session returns 200 with {"status":"noop"} and does not contact Google, so extensions can push on every cookie change. If the session is unhealthy, the push is verified again, so re-pushing can recover it.
  • The pushed header replaces GEMINI_COOKIES for later session refreshes and is saved to .cookies/pushed-session.json, so it survives a restart. Editing GEMINI_COOKIES afterwards makes the server use the edited value instead.
  • GET /admin/cookies (same bearer token) reports a short SHA-256 fingerprint of __Secure-1PSID, the cache age, the last update time and the update source. It never returns cookie values.

Configuration Priority

  1. Environment Variables (highest priority)
  2. .env file
  3. Defaults (lowest priority)

🧪 Usage Examples

Model selection

The provider discovers selectable models and their internal IDs from the signed-in account's Gemini Web model registry during session initialization and refresh. List the current choices with:

curl http://localhost:4981/openai/v1/models

Use a returned model ID in your requests. IDs are derived from Gemini's current display labels, such as gemini-3.6-flash and gemini-3.1-pro; they are not a fixed allowlist. Every selectable registry entry is returned, so newly added Gemini models become available without a code update. Category names returned by Gemini remain aliases, and gemini-advanced is retained as a compatibility alias for the discovered Pro category. An unavailable or unknown name returns an error instead of silently selecting another model.

Many accounts now get Gemini Web's Auto picker. These accounts list only gemini-auto: there is no separate Pro, Flash or Thinking model to select, because Gemini chooses the model for each request and uses Pro only when the account is allowed it. Use gemini-auto for every endpoint (and gemini-auto-canvas / gemini-auto-music for Canvas and music). On the OpenAI API, set reasoning_effort to low, medium or high to choose the thinking level. gemini-pro and gemini-advanced return HTTP 400 on these accounts, and the error message says to use gemini-auto. The examples in this README use gemini-auto. If your account lists Pro, you can use gemini-pro instead.

Model names are not seeded locally. When Google adds, removes, or renames a model, use the names returned by this endpoint instead of relying on old Gemini API version names.

The model field in a completion response contains the resolved versioned model ID (for example, a gemini-advanced request currently returns gemini-3.1-pro). Generated self-introductions are ordinary model output and can still be inaccurate.

Gemini Web may silently serve a lower-tier model after the selected model's quota is exhausted. The proxy detects an unambiguous different model ID in the protocol response and reports that model in model. The original caller value is preserved in requested_model. This keeps the response truthful without blocking a model that Gemini still advertises as selectable; after its quota resets, model returns to the requested version.

Thinking level (OpenAI-compatible chat)

Set reasoning_effort to low, medium, or high in POST /openai/v1/chat/completions, including streaming requests:

{
  "model": "gemini-3.6-flash",
  "reasoning_effort": "medium",
  "messages": [{"role": "user", "content": "Explain your answer."}]
}

Omitting this field preserves the existing low setting. The other OpenAI values are clamped to the nearest Gemini level (none and minimal → low, xhigh → high); anything else returns HTTP 400. The mapping is based on Gemini 3.6 Flash browser captures; availability of thinking levels on other models depends on Gemini Web.

When Gemini thinks, the reasoning it reports is Google's thought summary — the same status lines the Gemini web UI shows in its Thinking block (for example, "Analyzing travel's impact on finances"). They are surfaced as OpenAI's reasoning_content, Claude's thinking block, and Gemini's thought: true part. Consecutive repeated lines are collapsed, and the lines are newline-separated. This is not the model's full chain of thought: Gemini Web does not send a verbatim trace over the wire, so no proxy can expose one. An older inline <ctrl94>/<ctrl95> trace format is still parsed as a fallback if it reappears.

JSON responses (response_format, OpenAI-compatible chat)

POST /openai/v1/chat/completions accepts OpenAI's response_format, for both streaming and non-streaming requests:

{
  "model": "gemini-3.6-flash",
  "messages": [{"role": "user", "content": "Invent a person."}],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "person",
      "schema": {
        "type": "object",
        "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
        "required": ["name", "age"]
      }
    }
  }
}

Gemini Web has no JSON mode, so this is best-effort:

  • The prompt asks for JSON only, with no code fences and no text around it.
  • A json markdown code fence and any prose around the JSON are stripped before the reply is checked: json_object needs valid JSON that parses to an object, json_schema also needs the requested top-level type and every required key (deeper schema checks are not enforced). A reply that is broken as a whole is never answered with one of the objects nested inside it.
  • A reply that does not match is sent back to Gemini once for a repair. That is a second request on your Google account, so it never loops, with or without tools: if the repair is still invalid, the raw reply is returned and a warning is logged.
  • {"type": "text"} (the default) and unknown types are ignored. With tools, a reply without a tool call carries its JSON in the bridge's content; when the model calls a tool, the tool call is returned unchanged and JSON mode is skipped, so a tool call is never re-asked for JSON.

OpenAI SDK (Python)

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:4981/openai/v1",
    api_key="not-needed"
)

response = client.chat.completions.create(
    model="gemini-auto",
    messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)

Claude SDK (Python)

from langchain_anthropic import ChatAnthropic

llm = ChatAnthropic(
    base_url="http://localhost:4981/claude",
    model="gemini-auto",
    api_key="not-needed"
)

response = llm.invoke("Explain quantum computing")
print(response.content)

Gemini Native SDK (Python)

from google import genai

client = genai.Client(
    api_key="not-needed",
    http_options={
        "base_url": "http://localhost:4981/gemini",
        "api_version": "v1beta",
    },
)

response = client.models.generate_content(
    model="gemini-auto",
    contents="Write a poem about coding",
)
print(response.text)

Install the maintained SDK with pip install google-genai. Both regular generation and generate_content_stream are supported; the latter uses the SDK's alt=sse transport.

cURL

curl -X POST http://localhost:4981/openai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-auto",
    "messages": [{"role": "user", "content": "What is AI?"}],
    "stream": false
  }'

More examples are available in the examples/ directory.


📘 API Documentation

Once running, visit http://localhost:4981/docs for interactive API documentation powered by Scalar.

Scalar UI


🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

👤 Author


📄 License

This project is licensed under the MIT License — see the LICENSE file for details.


⭐ Star History

If you find this project useful, please consider giving it a star! ⭐


🔗 Links


Created with ❤️ by @ntthanh2603

About

✨Reverse-engineered API for Gemini web app. It can be used as a genuine API key from OpenAI, Gemini, and Claude.

Topics

Resources

Stars

636 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages

0