The service accepts `multipart/form-data` for generation. Management routes also accept form data. Responses are JSON unless noted. The examples use `curl`; an agent can translate them to Python `requests` or `httpx`.
## Credentials and first-time setup
There are two kinds of credentials:
- **Admin secret**: configured by the service operator. Send it as `x-admin-secret` to administrator endpoints, or `auth-secret` to provider-key endpoints.
- **Client secret**: configured when creating a client. Send it as `x-client-secret` when generating or polling. It can also be used as `auth-secret` for that client's provider-key endpoints.
Add an upstream API key. Use `google` or `openrouter`; lower `priority` values are tried first within the selected provider ordering.
```sh
curl -sS -X POST "$BASE_URL/api/keys"\
-H "auth-secret: $ADMIN_SECRET"\
-F 'client_id=booth-01'\
-F 'provider=google'\
-F 'api_key=YOUR_GOOGLE_API_KEY'\
-F 'priority=1'
```
The same endpoint may be authorized with the owning client's secret instead of the admin secret. Add more provider keys at priorities 2, 3, and so on for fallback.
## Generate synchronously
`POST /v1/generate` holds the HTTP connection until the provider returns. Form fields:
`x-client-secret` is required for every generation and job-poll request. Clients without a configured secret are rejected. Uploads accept JPEG, PNG, or WebP only (not GIF or SVG), up to 8 files and 50 MiB aggregate by default. The service decodes and re-encodes each image before forwarding it, stripping metadata and non-image payloads, and rejects malformed images, mismatched MIME types, and images larger than 25 megapixels by default. Operators can tune the limits.
`cost_usd` is the amount reported by OpenRouter for that generation. The proxy requests OpenRouter usage metadata and returns it under `usage`; it also includes OpenRouter's request ID and routing/timing metadata when available. For providers that do not return a billed amount (currently Google native), `cost_usd` is `null` and `cost_source` is `unavailable`. Completed async jobs expose these fields inside `result`.
Generation responses also include `generation_id`, `request_intake_sec`, and `total_sec`. `request_intake_sec` covers the request reaching and being parsed by the proxy before the generation is queued; `elapsed_sec` is the upstream call time; `total_sec` runs from request receipt through completion. These measurements help separate slow client uploads from slow model calls.
An image can be returned as `b64_json`, or as a URL when the upstream returns a hosted image. Decode `b64_json` as standard Base64 before saving it. Avoid printing or logging generated image data in an agent script.
## Generate asynchronously and poll
Add the query parameter `async_mode=true` to the same multipart request. The response returns as soon as the job is queued:
```sh
curl -sS -X POST "$BASE_URL/v1/generate?async_mode=true"\
On success, `status` is `completed` and a `result` field contains the synchronous response object. On failure, `status` is `failed` and `error` contains a sanitized message. Jobs are scoped to the owning `client_id`; an unknown job ID and a job belonging to another client both return `404`.
## Models and parameters
The built-in `nano-banana` alias maps to `gemini-2.5-flash-image` for Google and `google/gemini-2.5-flash-image` for OpenRouter by default. Operators can override these IDs with `GOOGLE_MODEL_NANO_BANANA` and `OPENROUTER_MODEL_NANO_BANANA`. Other model IDs pass through, with provider prefix normalization.
For OpenRouter, `reasoning` must be an object (for example `{"effort":"medium"}`); boolean values are omitted because the upstream API expects an object. Reasoning controls apply only to models that support them.
Provider APIs may reject unsupported parameters or model IDs. Configure exact IDs supported by the provider account. For a requested provider, fallback tries other active keys in configured priority order after a rate limit, server error, or connection/timeout failure. A non-retryable provider response stops the attempt sequence.
| `502` | Upstream failure after applicable failover attempts. Response contains status metadata, not upstream response bodies. |
## Notes for test scripts
The admin generations endpoint shows total, completed, and failed runs; sum of known costs; per-client cost and timing; and recent per-generation request intake, upstream, and total seconds. Prompts, images, and full provider keys are not included in this history.
- Aggregate cost and average timing fields include completed generations only. Failed runs remain visible in counts and recent history, but do not affect those totals or averages.
- Use a dedicated test client and avoid real production provider keys.
- A request with no configured provider key is useful for checking async job behavior: it should be accepted, then finish as `failed` with `No active provider keys configured`.
- A real image generation test requires an active provider key and can incur provider charges.
- The service currently has one in-process worker. Polling jobs survive client disconnects and completed results survive process restarts, but an active job is marked failed if the proxy restarts.