Every Gemini call has to identify itself somehow. There are three patterns, and the right one depends on who is acting:
API key — your application is the actor. Simplest pattern. Used for AI Studio.
OAuth 2.0 — an end user is the actor, and the app calls Gemini on their behalf. Used for things like the retrieval API where data is scoped to a Google account.
Service account — a server-side identity is the actor. Used for Vertex AI and any production GCP workload.
API key — the 30-second path
Get a key from AI Studio, set it as GEMINI_API_KEY (or GOOGLE_API_KEY; if both are set, GOOGLE_API_KEY wins), and the SDK reads it automatically. Never commit it. Never put it in client-side JavaScript. The blast radius of a leaked key is your bill, plus possibly your reputation if someone uses it to generate something embarrassing.
OAuth 2.0 — when the user owns the data
If your app needs to act as a logged-in user (their Drive files, their Gmail, their Generative Language retriever data), you need OAuth. The flow is the standard Google OAuth dance: redirect to consent screen, exchange code for refresh + access tokens, refresh as needed.
Service accounts — the production default on Vertex
On Vertex AI, the recommended pattern is Application Default Credentials (ADC). The SDK picks up credentials from the environment: a service account JSON, a workload identity binding, or your local gcloud auth application-default login. No keys in code.
Code
API key path (AI Studio)·bash
# .env (gitignored)
GEMINI_API_KEY=AIzaSy...
# Or for a one-off shell
export GEMINI_API_KEY=AIzaSy...
API key in code·python
from google import genai
# Reads GEMINI_API_KEY (or GOOGLE_API_KEY) from env
client = genai.Client()
# Or pass explicitly (less common; useful in tests)
client = genai.Client(api_key='AIzaSy...')
# Raw HTTP equivalent
# curl 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent' \
# -H "x-goog-api-key: $GEMINI_API_KEY" \
# -H 'Content-Type: application/json' \
# -d '{"contents":[{"parts":[{"text":"Hello"}]}]}'
OAuth — user-acting flow·python
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
import os
SCOPES = ['https://www.googleapis.com/auth/generative-language.retriever']
def load_creds():
creds = None
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'client_secret.json', SCOPES)
creds = flow.run_local_server(port=0)
with open('token.json', 'w') as f:
f.write(creds.to_json())
return creds
Service account / ADC (Vertex)·python
from google import genai
# Local dev: `gcloud auth application-default login` once,
# then ADC works automatically.
# Server: GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa.json
# (or workload identity if on GKE/Cloud Run)
client = genai.Client(
vertexai=True,
project='my-prod-project',
location='us-central1',
)
# No keys in code. ADC resolves the right credential.
Set up two ways to authenticate against Gemini in a fresh Python venv: (1) API key from a local .env, (2) the OAuth flow above against a personal Google account. Make a single generateContent call through each path and confirm both work. Write one sentence about which pattern you'd choose for your next project and why.
Progress
Progress is local-only — sign in to sync across devices.