Unofficial Gemini API (gemini_webapi)
When implementing code that uses Google's Gemini through unofficial means (reverse-engineered web API), use the gemini_webapi library from HanaokaYuzu/Gemini-API.
Quick Reference
from gemini_webapi import GeminiClient
import asyncio
# Authentication (see section below for details)
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
# Single generation
response = await client.generate_content("Your prompt here")
print(response.text)
# Multi-turn conversation
chat = client.start_chat()
response = await chat.send_message("Hello!")
print(response.text)
Authentication
Option 1: Environment Variables (Recommended)
Store cookies in a .env file:
# .env
GEMINI_SECURE_1PSID=your_cookie_value_here
GEMINI_SECURE_1PSIDTS=your_cookie_value_here
Then load in Python:
import os
from dotenv import load_dotenv
from gemini_webapi import GeminiClient
import asyncio
load_dotenv()
async def main():
client = GeminiClient(
secure_1psid=os.getenv("GEMINI_SECURE_1PSID"),
secure_1psidts=os.getenv("GEMINI_SECURE_1PSIDTS")
)
await client.init()
asyncio.run(main())
Option 2: Cookie File (Netscape format)
Export cookies from your browser in Netscape format using an extension like "Get cookies.txt LOCALLY" or "Cookie-Editor" (choose Netscape export).
The file uses tab-separated fields: domain, include_subdomains, path, secure, expires, name, value:
# Netscape HTTP Cookie File
.google.com TRUE / TRUE 1809084518 __Secure-1PSID your_value_here
.google.com TRUE / TRUE 1809084518 __Secure-1PSIDTS your_value_here
Load from file:
def load_cookies_from_netscape(filepath="cookie.txt"):
"""Load __Secure-1PSID and __Secure-1PSIDTS from a Netscape-format cookie.txt.
Args:
filepath (str): Path to the Netscape-format cookie file. Defaults to "cookie.txt".
Returns:
dict: Cookie name-value pairs for __Secure-1PSID and __Secure-1PSIDTS.
"""
cookies = {}
with open(filepath) as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
parts = line.split('\t')
if len(parts) >= 7:
name, value = parts[5], parts[6]
if name in ("__Secure-1PSID", "__Secure-1PSIDTS"):
cookies[name] = value
return cookies
cookies = load_cookies_from_netscape()
client = GeminiClient(cookies.get("__Secure-1PSID"), cookies.get("__Secure-1PSIDTS"))
Option 3: Browser Cookie Auto-Import (Simplest)
Install with browser support:
pip install -U gemini_webapi[browser]
Then just initialize - cookies are automatically imported:
from gemini_webapi import GeminiClient
import asyncio
async def main():
# Make sure you're logged in to https://gemini.google.com
client = GeminiClient()
await client.init()
asyncio.run(main())
Getting Cookies Manually
If you need to get cookies manually:
- Go to https://gemini.google.com and log in
- Press F12 to open DevTools, go to the Network tab
- Refresh the page
- Click any request and look in the Request Headers > Cookie
- Copy the values for
__Secure-1PSIDand__Secure-1PSIDTS
Installation
pip install -U gemini_webapi
# Or for browser auto-cookie support:
pip install -U gemini_webapi[browser]
Core Usage Patterns
1. Single Prompt Generation
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
response = await client.generate_content("Explain quantum computing")
print(response.text)
asyncio.run(main())
2. Multi-Turn Conversation
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
chat = client.start_chat()
response1 = await chat.send_message("What's the capital of France?")
print(response1.text)
response2 = await chat.send_message("What about Germany?")
print(response2.text)
asyncio.run(main())
3. Streaming Mode (for real-time output)
text_delta contains only the new characters since the last yield. Also works in multi-turn via chat.send_message_stream().
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
# Single-turn streaming
async for chunk in client.generate_content_stream("Tell me a long story"):
print(chunk.text_delta, end="", flush=True)
print()
# Multi-turn streaming
chat = client.start_chat()
async for chunk in chat.send_message_stream("Continue the story"):
print(chunk.text_delta, end="", flush=True)
print()
asyncio.run(main())
4. File Upload (Images, Documents)
from pathlib import Path
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
response = await client.generate_content(
"What's in this image?",
files=["screenshot.png", "document.pdf"]
)
print(response.text)
asyncio.run(main())
5. Select Model
Available models (as of 1.21.0): gemini-3.1-pro, gemini-3.0-flash, gemini-3.0-flash-thinking. model works on both generate_content and start_chat.
from gemini_webapi import GeminiClient
from gemini_webapi.constants import Model
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
# Via generate_content
response = await client.generate_content(
"Solve: What is the derivative of x^2?",
model=Model.G_3_0_FLASH # or string: "gemini-3.0-flash"
)
print(response.text)
# Via start_chat
chat = client.start_chat(model="gemini-3.1-pro")
response2 = await chat.send_message("What is 247 * 313?")
print(response2.text)
# Custom model with raw header (for unlisted models)
custom_model = {
"model_name": "xxx",
"model_header": {
"x-goog-ext-525001261-jspb": "[1,null,null,null,'e6fa609c3fa255c0',null,null,null,[4]]"
},
}
response3 = await client.generate_content("Hello", model=custom_model)
asyncio.run(main())
6. Using Gems (System Prompts)
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
# NOTE: fetch_gems() is currently broken in gemini_webapi 1.21.0 due to a Google API
# response format change (see https://github.com/HanaokaYuzu/Gemini-API/issues/279).
# Awaiting upstream fix. The code below is correct; the failure is server-side.
await client.fetch_gems(include_hidden=False)
# Use a predefined gem
coding_gem = client.gems.filter(predefined=True).get(id="coding-partner")
response = await client.generate_content(
"Help me debug this code",
gem=coding_gem
)
print(response.text)
asyncio.run(main())
7. Image Generation
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
# Ask to generate images (not just fetch web images)
response = await client.generate_content("Generate 3 pictures of cats")
# Save generated images
for i, image in enumerate(response.images):
await image.save(path="output/", filename=f"cat_{i}.png")
print(f"Saved: {image.title}")
asyncio.run(main())
8. Using Extensions (@gmail, @youtube, etc.)
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
# Must activate extensions on Gemini website first
response = await client.generate_content("@gmail Summarize my latest email")
print(response.text)
asyncio.run(main())
9. Retrieve Model Thoughts
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
response = await client.generate_content(
"What is 247 * 313?",
model="gemini-3.1-pro"
)
print("Thoughts:", response.thoughts)
print("Answer:", response.text)
asyncio.run(main())
10. Persistent Chat (Save/Restore)
from gemini_webapi import GeminiClient
import json
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
# Start a chat
chat = client.start_chat()
await chat.send_message("My name is Alice")
# Save chat metadata (in-memory or persist to file)
previous_session = chat.metadata
# To persist across processes, serialize as JSON:
# with open("chat_state.json", "w") as f:
# json.dump(list(previous_session), f)
# with open("chat_state.json") as f:
# previous_session = tuple(json.load(f))
# Restore the conversation
restored_chat = client.start_chat(metadata=previous_session)
response = await restored_chat.send_message("What's my name?")
print(response.text) # Should remember "Alice"
asyncio.run(main())
11. Temporary Mode (No history saved)
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
# This won't be saved in Gemini history
response = await client.generate_content(
"Secret message",
temporary=True
)
print(response.text)
asyncio.run(main())
12. Managing Custom Gems
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
# Create a custom gem
new_gem = await client.create_gem(
name="My Assistant",
prompt="You are a helpful coding assistant specialized in Python.",
description="Python coding helper"
)
print(f"Created gem: {new_gem.id}")
# Use the custom gem
response = await client.generate_content(
"Write a Fibonacci function",
gem=new_gem.id
)
print(response.text)
# Update the gem
updated = await client.update_gem(
gem=new_gem,
name="My Python Assistant",
prompt="You are an expert Python tutor.",
description="Advanced Python help"
)
# Delete the gem when done
await client.delete_gem(updated)
asyncio.run(main())
13. Multiple Reply Candidates
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
chat = client.start_chat()
response = await chat.send_message("Recommend a sci-fi book")
# Check all candidates
for i, candidate in enumerate(response.candidates):
print(f"Candidate {i+1}:")
print(candidate.text)
print("---")
# Choose a specific candidate to continue with
if len(response.candidates) > 1:
chat.choose_candidate(index=1)
followup = await chat.send_message("Tell me more about that one")
print(followup.text)
asyncio.run(main())
14. Read/Delete Chat History
from gemini_webapi import GeminiClient
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init()
# Start a chat
chat = client.start_chat()
await chat.send_message("What's 2+2?")
# Read chat history
history = await client.read_chat(chat.cid)
for turn in history:
print(f"User: {turn.user_prompt}")
print(f"Assistant: {turn.assistant_response}")
print("---")
# Delete the chat
await client.delete_chat(chat.cid)
print(f"Deleted chat: {chat.cid}")
asyncio.run(main())
15. Async Chatbot Example
from gemini_webapi import GeminiClient
import asyncio
from typing import Optional
class GeminiBot:
def __init__(self, secure_1psid: str, secure_1psidts: str):
self.client = GeminiClient(secure_1psid, secure_1psidts)
self.chat: Optional[object] = None
async def start(self):
await self.client.init()
self.chat = self.client.start_chat()
async def message(self, prompt: str) -> str:
if not self.chat:
await self.start()
response = await self.chat.send_message(prompt)
return response.text
# Usage
async def main():
bot = GeminiBot(secure_1psid, secure_1psidts)
answer = await bot.message("Hello!")
print(answer)
asyncio.run(main())
Advanced Configuration
Docker Cookie Persistence
# docker-compose.yml
services:
gemini-bot:
environment:
- GEMINI_COOKIE_PATH=/tmp/gemini_webapi
volumes:
- ./gemini_cookies:/tmp/gemini_webapi
init() Parameters
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
await client.init(
timeout=30, # connection timeout in seconds
auto_close=True, # close client after inactivity (good for chatbots)
close_delay=300, # inactivity threshold in seconds
auto_refresh=True, # auto-refresh __Secure-1PSIDTS in background (default True)
)
asyncio.run(main())
Logging
from gemini_webapi import set_log_level
set_log_level("DEBUG") # DEBUG, INFO, WARNING, ERROR, CRITICAL
Important Notes
Python 3.10+ is required
Cookies auto-refresh - The library automatically refreshes
__Secure-1PSIDTSin the background as long as the process is running.Image generation availability - Varies by region/account. Google restricts this feature in some regions.
Extensions - Must be activated on the Gemini website first before using via API.
Rate limiting - This uses the web interface, so be mindful of rate limits to avoid being flagged.
Terms of Service - This is an unofficial, reverse-engineered API. Use at your own risk and in accordance with Google's terms of service.
Response Object Structure
When calling generate_content() or send_message(), you get a ModelOutput object with:
text: The main response textthoughts: Model's internal thought process (if available)images: List ofImageobjects (web or generated)candidates: List of alternative response candidatesfiles: Uploaded files referenced in the response
Error Handling
from gemini_webapi import GeminiClient
from gemini_webapi.exceptions import GeminiAPIError
import asyncio
async def main():
client = GeminiClient(secure_1psid, secure_1psidts)
try:
await client.init(timeout=30)
response = await client.generate_content("Hello")
print(response.text)
except GeminiAPIError as e:
print(f"Gemini API error: {e}")
except asyncio.TimeoutError:
print("Connection timed out")
asyncio.run(main())