An asynchronous proxy for image generation through Google Gemini or OpenRouter. It supports multipart uploads, key rotation, direct long-running responses, and SQLite-backed polling jobs.
The service refuses to start without `ADMIN_SECRET`. It creates the SQLite database and data directory at startup. Open `http://127.0.0.1:8000/ui` for basic administration. The first client and provider key can be created through the UI or API.
## Configure a client
Create a client as an administrator:
```sh
curl -X POST http://127.0.0.1:8000/api/admin/client \
`ADMIN_SECRET` and client secrets are stored only as SHA-256 hashes for client authentication. Upstream provider keys must be retained to make requests, so they are stored in SQLite. Protect the database file and its backups as secrets.
## Generate
Direct mode holds the connection open until generation completes:
```sh
curl -X POST http://127.0.0.1:8000/v1/generate \
-H 'x-client-secret: choose-a-client-secret'\
-F client_id=booth-01 -F model=nano-banana \
-F prompt='Restyle the subject using the second image as a visual reference'\
Polling mode accepts the same multipart fields with `?async_mode=true`. It returns a `job_id`; poll the returned URL with `client_id` as a query parameter and the same `x-client-secret` header. Job state and compact result metadata are stored in SQLite; generated images are converted to WebP (quality 90 by default) and stored as private files under `OUTPUT_DIR` (default `./data/generated`). Downloaded async results use `image/webp`, so save them with a `.webp` extension or convert them locally to JPEG/PNG if the next step requires it. Output files are automatically deleted after 24 hours (checked at startup and every five minutes); job, timing, and cost records remain. Once a job completes or fails, its uploaded prompt/image request payload is removed from SQLite. Jobs still processing when the proxy process restarts are marked failed because the in-process worker cannot resume safely.
The built-in `nano-banana` alias defaults to Gemini 2.5 Flash Image. Set `GOOGLE_MODEL_NANO_BANANA` and `OPENROUTER_MODEL_NANO_BANANA` to provider model IDs you have confirmed are available for your account. Other model IDs are passed through (with `google/` prefix normalization for the native provider).
## Configuration
| Variable | Default | Purpose |
| --- | --- | --- |
| `ADMIN_SECRET` | required | Protects administration endpoints. Use a long random value. |
| `GOOGLE_MODEL_NANO_BANANA` | `gemini-2.5-flash-image` | Native Google alias target. |
| `OPENROUTER_MODEL_NANO_BANANA` | `google/gemini-2.5-flash-image` | OpenRouter alias target. |
Only one Uvicorn worker is supported for this initial implementation. SQLite WAL is intended for a local persistent disk, not a shared network filesystem. Configure Nginx body and proxy timeouts to match your deployment limits; see [`deploy/nginx.conf`](deploy/nginx.conf).
## API outline
See the copyable endpoint guide in [`docs/API.md`](docs/API.md).
-`GET /api/admin/state` — client and redacted key state (`x-admin-secret`).
-`POST /api/admin/client` — create/update a client (`x-admin-secret`).
-`POST /api/keys` and `DELETE /api/keys/{id}` — manage keys with `auth-secret` (admin or owning client secret).
-`GET /healthz` and `GET /ui`.
Errors use FastAPI's `{"detail": ...}` envelope. Provider failures return HTTP 502 with provider names and status codes only; upstream response bodies and credentials are not returned.
## Deploy with Docker
```sh
docker build -t phproxy .
docker run --name phproxy --restart unless-stopped \
-p 127.0.0.1:8000:8000 \
-e ADMIN_SECRET="$ADMIN_SECRET"\
-e DB_PATH=/data/proxy.db \
-v phproxy-data:/data \
phproxy
```
Terminate TLS at a trusted reverse proxy and keep the application port private. The sample Nginx configuration is a location snippet; add your certificate and domain configuration separately.