Files
ph_api/README.md
T

4.9 KiB
Raw Blame History

phproxy

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.

Start locally

Requirements: Python 3.12 or newer. Uploaded images are decoded and re-encoded with Pillow; only JPEG, PNG, and WebP are accepted.

python -m venv .venv
. .venv/bin/activate
pip install -e .
cp .env.example .env
# Set ADMIN_SECRET to a unique random value before starting.
set -a && . ./.env && set +a
uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 1

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:

curl -X POST http://127.0.0.1:8000/api/admin/client \
  -H "x-admin-secret: $ADMIN_SECRET" \
  -F client_id=booth-01 -F name='Booth 01' -F client_secret='choose-a-client-secret'

Add an upstream key (the client secret may also add its own keys):

curl -X POST http://127.0.0.1:8000/api/keys \
  -H "auth-secret: $ADMIN_SECRET" \
  -F client_id=booth-01 -F provider=google -F api_key="$GOOGLE_API_KEY" -F priority=1

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:

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' \
  -F params='{"aspect_ratio":"9:16","image_size":"2K"}' \
  -F 'images=@capture.jpg;type=image/jpeg' -F 'images=@style.png;type=image/png'

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. Jobs and their results are stored in SQLite so they survive a client disconnect. 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.
DB_PATH ./data/proxy.db SQLite database location.
MAX_UPLOAD_BYTES 52428800 Aggregate raw image upload limit.
MAX_IMAGES 8 Maximum reference image count.
MAX_IMAGE_PIXELS 25000000 Maximum width × height for each uploaded image.
HTTP_TIMEOUT_SECONDS 300 Overall upstream request timeout.
CONNECT_TIMEOUT_SECONDS 15 Upstream connect timeout.
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.

API outline

See the copyable endpoint guide in docs/API.md.

  • POST /v1/generate — multipart client_id, prompt, model, optional provider, JSON params, repeated images; optional ?async_mode=true.
  • GET /v1/jobs/{job_id}?client_id=... — job status/result, tenant-scoped.
  • 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

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.