Provide a small single-host service that routes image-generation requests from installation clients to configured Google Gemini or OpenRouter API keys. It must tolerate long upstream response times, multiple reference images, client disconnects in polling mode, and upstream key/provider failures.
## Users and operating model
- Photobooth/installation clients submit a prompt, model, optional parameters, and zero or more images.
- An administrator provisions clients and manages access.
- The initial service runs as one Uvicorn process against a local SQLite database on persistent disk.
## Requirements
### Functional
1. Accept multipart image-generation requests at `POST /v1/generate`.
2. Support direct long-hold responses and `async_mode=true` polling jobs.
3. Support Google native Gemini and OpenRouter adapters, including multiple uploaded images and normalized image/text outputs.
4. Resolve named model aliases from configuration and pass through explicit model IDs.
5. Store multiple provider keys per client and try keys by priority, preferring the requested provider first.
6. Retry another configured key/provider for rate-limit, server, and connection/timeout failures. Do not retry malformed or rejected requests as if a provider were unavailable.
7. Persist clients, provider keys, jobs, and job results in SQLite WAL mode.
8. Provide a minimal browser UI and admin/client key-management API.
9. Return upstream-reported usage and per-generation USD cost when the provider supplies it.
10. Record request intake, upstream, and total execution time, provider/model selection, failures, image count/bytes, and reported cost. Show recent records and aggregates in the admin UI.
### Non-functional and security
- Use async HTTP for all upstream network I/O and a shared bounded connection pool.
- Bound aggregate uploaded bytes, image count, and decoded pixel dimensions. Accept only valid JPEG, PNG, and WebP images; re-encode before forwarding to strip metadata and trailing payloads.
- Require an explicit administrator secret at startup; never ship a default secret.
- Store client secrets hashed. Store provider keys only because they are needed for upstream requests; restrict database file access and redact keys from API responses.
- Scope job reads and client key operations to the authenticated owning client.
- Never return upstream response bodies or internal exception traces to callers.
- Keep polling job ownership in the database. A process restart must not leave a job forever marked as running.
- Use a single app worker with local SQLite in this release. Horizontal scaling and durable background execution are out of scope.
Success is `200` with `provider_used`, `model_used`, `images`, `text`, elapsed time, and failover attempt count. With `?async_mode=true`, success is `200` with `job_id`, status, and poll URL.
### Poll
`GET /v1/jobs/{job_id}?client_id={client_id}` plus the client's secret header. Returns status and, when complete, result; terminal failures include a sanitized error. Unknown and cross-client job IDs return `404`.
### Management
- Admin routes use `x-admin-secret`.
- Key create/delete uses `auth-secret`, accepted only for the administrator or the owning client.
- Admin state returns only a short key prefix and never a full key.
Validation errors use FastAPI's `detail` format; upstream failures return `502` with sanitized attempt metadata.
-`provider_keys`: owned by a client with cascading deletion; provider constrained to `google`/`openrouter`; key, priority, activation and failure metadata.
-`jobs`: owned by a client with cascading deletion; status, serialized request/result/error and timestamps. On startup, interrupted jobs are marked failed.
SQLite connections enable foreign keys and busy timeout; WAL is initialized once the DB opens. Provider-route and job-owner lookups are indexed. The DB and backups contain upstream credentials and potentially image data in queued job payloads/results, so filesystem and backup permissions are part of deployment security.
## Architecture and flow
1. FastAPI validates the multipart request and client authentication.
- [x] Add local startup documentation and Docker/Nginx deployment files.
- [x] Add per-generation execution and cost history to the admin API/UI.
- [ ] Run deployment smoke checks with actual provider credentials and confirm account/model availability before production.
## Out of scope / known limits
- Jobs are persisted but are executed in-process; a service restart marks active jobs failed rather than resuming them.
- SQLite WAL assumes local storage and one app process. Multi-host operation needs a different DB and durable queue.
- Upstream model availability and provider-specific parameter support vary by account and change over time. Configure and validate model IDs per provider.
- The initial admin portal has no user/session system; protect it behind network access controls and HTTPS.
- Memory use grows with uploaded image size and serialized base64/job results; the service is asynchronous, not zero-copy or constant-memory.