# Gemini API key (local development)

**Last updated:** 2026-04-10  

Local/offline tooling (blog featured images, OG Gemini pipeline, FAQ/SEO helpers, Nano/mobile/partner analysis scripts) uses the **Gemini Developer API** over REST: `generativelanguage.googleapis.com`. This is **not** Vertex AI (no service account required for the current codebase).

**Production** (live site, Firmennamen API, server-side PHP) uses a **separate** key and rules — see [`GEMINI_API_KEY_PRODUCTION.md`](GEMINI_API_KEY_PRODUCTION.md).

## Two keys (summary)

| Variable | Where | Purpose |
|----------|--------|---------|
| `GEMINI_LOCAL_API_KEY` | Developer `.env` (recommended) | Local scripts and non-production PHP |
| `GEMINI_API_KEY` | Legacy local **or** production server | Non-prod: fallback after `GEMINI_LOCAL_API_KEY`. **Production:** only source for live traffic (see production doc) |

Resolution is implemented in `ordio_get_gemini_api_key()` (`v2/config/ai-faq-config.php`) and `v2/config/gemini-environment.php`. **Python** CLI mirrors this in `v2/scripts/gemini_api_key.py` using `ORDIO_ENV=production` for production context (no `HTTP_HOST` in typical CLI).

### How to test “production-like” flows on your laptop

| Goal | Approach |
|------|----------|
| **Normal local dev** (Firmennamen page, scripts) | Use **`GEMINI_LOCAL_API_KEY`** or legacy **`GEMINI_API_KEY`** in `.env` / `gemini-api-key.php`. **`localhost` is not production** (`HTTP_HOST` is not `*.ordio.com`), so the resolver uses the **local** chain — you do **not** need the production server key. |
| **Match production key resolution in PHP** | Rarely needed: set **`ORDIO_ENV=production`** in the PHP process **and** **`GEMINI_API_KEY`** to a key you are willing to bill (prefer a **staging** project key, not the live prod key, to avoid quota/cost risk from a laptop). |
| **Match production in Python CLI** | Same: `ORDIO_ENV=production` + `GEMINI_API_KEY`. |
| **Real production validation** | Prefer **staging** or **SSH on the server** with `GEMINI_API_KEY_PRODUCTION.md` smoke steps — not the literal production secret on every developer machine. |

**403 on `localhost` when calling the Firmennamen API** is usually **CORS** (browser `Origin` not allowlisted), not Gemini. With default settings, the API merges an allowlist entry from `HTTP_HOST` for `localhost` / `127.0.0.1` / `*.local`; if you set **`FIRMENNAMEN_CORS_ORIGINS`** yourself, include your dev origin explicitly. A **403 from Gemini** after a successful CORS check usually means API key restrictions (e.g. HTTP referrer) — use a server-style key with **Application restrictions → None** for REST, per troubleshooting below.

## What to create in Google Cloud / AI Studio (local key)

Create **one API key** for your **local / team dev** project, with the **Generative Language API** enabled (separate from the production billing project if you want clean cost attribution).

1. **Open [Google AI Studio → API keys](https://aistudio.google.com/app/apikey)** (or [Google Cloud Console → APIs & Services → Credentials](https://console.cloud.google.com/apis/credentials)).
2. **Create an API key** (or use an existing key that is only used for Gemini).
3. In **Google Cloud Console** for the same project: **APIs & Services → Library** → enable **Generative Language API** (`generativelanguage.googleapis.com`) if it is not already enabled.
4. **Restrict the key (recommended):** Credentials → select the key → **API restrictions** → restrict to **Generative Language API** only.  
   Per [Google’s docs](https://ai.google.dev/gemini-api/docs/api-key), keys shown in AI Studio must be unrestricted *or* restricted **only** to the Generative Language API.
5. **Application restrictions (local CLI):** For scripts run on your laptop, **None** is typical. Optional: **IP addresses** if you have a stable egress IP. Do **not** rely on HTTP referrer restrictions for server-side REST calls (those apply to browser keys, e.g. Maps).

## Migration from a single `GEMINI_API_KEY` in `.env`

If you previously set only `GEMINI_API_KEY` for local work:

1. Add **`GEMINI_LOCAL_API_KEY=<same value>`** (or rename mentally: put the dev key in `GEMINI_LOCAL_API_KEY`).
2. Optionally remove `GEMINI_API_KEY` from your laptop `.env` so you do not confuse it with the **production** secret (recommended once production is deployed with its own key).
3. Old behavior is preserved: in **non-production** context, `GEMINI_API_KEY` is still read after `GEMINI_LOCAL_API_KEY`.

## Model overrides (optional)

For local scripts, you can steer models without editing code (see **`docs/development/GEMINI_OPTIMIZATION_GUIDE.md`**):

| Variable | Typical default | Used by |
|----------|-----------------|---------|
| `GEMINI_TEXT_MODEL` | `gemini-2.5-flash` | PHP text endpoints, `check-gemini-quota.py` probe |
| `GEMINI_TEXT_MODEL_FALLBACK` | `gemini-2.5-flash-lite` | FAQ dual-model retry (PHP) |
| `GEMINI_VISION_MODEL` | `gemini-2.5-flash` | Python screenshot/asset analyzers |
| `GEMINI_IMAGE_MODEL` | `gemini-2.5-flash-image` | Blog featured image + OG Gemini pipelines |

PHP reads the first two via `v2/config/gemini-models.php`. Image pricing is not token-only — check [pricing](https://ai.google.dev/gemini-api/docs/pricing) before upgrading to preview/pro image models.

## Billing and quotas

- **Rough per-workflow cost estimates** (Standard tier, illustrative token assumptions): [`GEMINI_OPTIMIZATION_GUIDE.md`](GEMINI_OPTIMIZATION_GUIDE.md) — section *Pricing estimates*.
- Free tier and rate limits apply per project; see [AI Studio usage](https://aistudio.google.com/usage) and [Gemini API rate limits](https://ai.google.dev/gemini-api/docs/rate-limits).
- **Image** models (`gemini-2.5-flash-image`, optional `GEMINI_IMAGE_MODEL` such as `gemini-3-pro-image-preview`) may return **429 / RESOURCE_EXHAUSTED** if quotas are exceeded or billing is required for your tier. Check quotas and billing in GCP if generation fails while text calls work.

## Do not reuse Google Maps keys

Maps keys are often **HTTP referrer–restricted** and authorized for Maps APIs, not the Generative Language API. Use a **dedicated** Gemini key. The repo no longer falls back from Gemini to `GOOGLE_MAPS_API_KEY`.

## Where to put the key locally (SSOT)

**Recommended:** repo-root **`.env`** (gitignored):

```bash
GEMINI_LOCAL_API_KEY=your-dev-key-here
```

Transitional (still supported on non-production):

```bash
GEMINI_API_KEY=your-dev-key-here
```

**Optional:** copy `v2/config/gemini-api-key.php.example` to `v2/config/gemini-api-key.php` and set `api_key` (file is gitignored). In **production**, this file is used **after** `GEMINI_API_KEY` env (same pattern as ShiftOps `google-maps-shiftops-api-key.php`); see [`GEMINI_API_KEY_PRODUCTION.md`](GEMINI_API_KEY_PRODUCTION.md).

### Resolution order — **non-production** (PHP and Python)

1. `GEMINI_LOCAL_API_KEY` (env)
2. `GEMINI_API_KEY` (env, legacy local)
3. PHP constant `GEMINI_API_KEY` (PHP only)
4. `v2/config/gemini-api-key.php` (`api_key` field)
5. Python: `v2/scripts/gemini_api_key.py` invokes `ordio_get_gemini_api_key()` via PHP when needed

### Resolution order — **production** (PHP web on `*.ordio.com`, or `ORDIO_ENV=production`)

1. `GEMINI_API_KEY` (env) or PHP constant `GEMINI_API_KEY`  
2. `v2/config/gemini-api-key.php` (`api_key` field) if env is unset — mirrors ShiftOps `GOOGLE_MAPS_SHIFTOPS_API_KEY` → `google-maps-shiftops-api-key.php`. **Env wins:** a bad value in FPM overrides a good file until you fix or remove the env line.

**Python on the server:** export **`ORDIO_ENV=production`** and **`GEMINI_API_KEY`** so CLI matches PHP; subprocess inherits env.

**Note:** Official Gemini SDKs may read `GOOGLE_API_KEY` and take it over `GEMINI_API_KEY`. This repo’s REST scripts standardize on **`GEMINI_API_KEY`** for production and **`GEMINI_LOCAL_API_KEY`** for local preference; avoid setting conflicting values.

## Rotation (local key)

1. Create a **new** API key in AI Studio or GCP.
2. Update **`.env`** (`GEMINI_LOCAL_API_KEY`) and/or `v2/config/gemini-api-key.php`.
3. Verify (see below).
4. **Delete the old key** in GCP Credentials.

## Verification

```bash
php v2/scripts/tools/test-gemini-key-resolution.php
python3 v2/scripts/nano-ai/check-gemini-quota.py
php v2/scripts/tools/test-gemini-models.php
python3 v2/scripts/og-images/generate-og-image-gemini.py --dry-run --type=tools
```

## Troubleshooting

### Häufiger Irrtum: `localhost` unter „Websites“ reicht nicht

Wenn du in Google Cloud **Application restrictions → Websites** nutzt und `http://localhost:8003/*` einträgst, schlägt der **Firmennamen-Generator** (und jeder serverseitige PHP/cURL-Aufruf) trotzdem fehl mit `API_KEY_HTTP_REFERRER_BLOCKED` / „referer &lt;empty&gt;“.

**Grund:** Der Browser lädt zwar die Seite von `localhost`, aber **PHP auf dem Server** spricht **direkt** mit `generativelanguage.googleapis.com` und sendet **keinen** HTTP-`Referer` wie ein Browser-Tab. Website-Beschränkungen gelten für Browser-Anfragen mit Referer, nicht für diesen Aufruf.

**Lösung:** Für denselben Key **Application restrictions → None** (empfohlen lokal) oder **IP addresses** (wenn feste Server-IP). **API restrictions** kann weiter **Generative Language API** sein — nur die *Application*-Restriktion muss server-tauglich sein.

| Symptom | Likely cause |
|--------|----------------|
| Firmennamen-Generator lokal: **HTTP 500**, Meldung „vorübergehend nicht verfügbar“, in den Logs `Gemini API 403` / `API_KEY_HTTP_REFERRER_BLOCKED` | Der API-Key hat **HTTP-Referrer**-Beschränkung (Browser-Keys). Für PHP/cURL am Server: **Application restrictions → None** (oder feste IP), nicht „HTTP referrers“. |
| 403 / API key not valid | Wrong key, or Generative Language API not enabled for the project |
| 403 `API_KEY_HTTP_REFERRER_BLOCKED` / "Requests from referer &lt;empty&gt; are blocked" | The key has **HTTP referrer** (or other client) restrictions meant for browser keys. For CLI/REST, create a **new** key with **Application restrictions → None** (or IP if you use a fixed egress), or use a separate server-side key. |
| 403 after restricting key | API restriction list missing Generative Language API |
| 429 / RESOURCE_EXHAUSTED | Rate or daily quota; reduce concurrency; check billing/tier |
| Key works in PHP but not Python | Export `GEMINI_LOCAL_API_KEY` / `GEMINI_API_KEY` in the shell or add `gemini-api-key.php` |

## References

- [Using Gemini API keys](https://ai.google.dev/gemini-api/docs/api-key)
- [Adding restrictions to API keys (GCP)](https://cloud.google.com/api-keys/docs/add-restrictions-api-keys)
- [API keys best practices (GCP)](https://cloud.google.com/docs/authentication/api-keys-best-practices)
