Files
ph_api/docs/API.md
T

180 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# phproxy API
Production base URL: `https://ph_proxy.sokaris.link` (local development uses `http://127.0.0.1:8000`).
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.
Set `ADMIN_SECRET` in the service environment. Do not commit it or include it in client scripts.
Create a client:
```sh
BASE_URL=https://ph_proxy.sokaris.link
ADMIN_SECRET='value-from-the-service-environment'
curl -sS -X POST "$BASE_URL/api/admin/client" \
-H "x-admin-secret: $ADMIN_SECRET" \
-F 'client_id=booth-01' \
-F 'name=Booth 01' \
-F 'client_secret=replace-with-a-client-secret'
```
Response:
```json
{"status":"ok","client_id":"booth-01"}
```
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:
| Field | Required | Meaning |
| --- | --- | --- |
| `client_id` | Yes | Provisioned client identifier. |
| `prompt` | No | Text prompt. Defaults to an empty string. |
| `model` | No | Provider model ID or alias. Defaults to `nano-banana`. |
| `provider` | No | Preferred provider: `google` or `openrouter`. If omitted, configured key order is used. |
| `params` | No | JSON object encoded as a string. Defaults to `{}`. |
| `images` | No | Repeat this multipart field for each reference image. |
`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.
```sh
curl -sS -X POST "$BASE_URL/v1/generate" \
-H 'x-client-secret: replace-with-a-client-secret' \
-F 'client_id=booth-01' \
-F 'prompt=Restyle the person using the second image as a style reference' \
-F 'model=nano-banana' \
-F 'provider=google' \
-F 'params={"aspect_ratio":"9:16","image_size":"2K"}' \
-F 'images=@capture.jpg;type=image/jpeg' \
-F 'images=@style.png;type=image/png'
```
Success (`200`):
```json
{
"provider_used": "openrouter",
"model_used": "google/gemini-2.5-flash-image",
"images": [{"mime_type": "image/png", "b64_json": "..."}],
"text": "",
"cost_usd": 0.0412,
"cost_source": "openrouter_usage",
"usage": {"prompt_tokens": 1200, "completion_tokens": 1290, "cost": 0.0412},
"elapsed_sec": 12.34,
"failover_attempts": 0
}
```
`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" \
-H 'x-client-secret: replace-with-a-client-secret' \
-F 'client_id=booth-01' \
-F 'prompt=Make a cinematic portrait' \
-F 'model=nano-banana' \
-F 'images=@capture.jpg;type=image/jpeg'
```
Queued response (`200`):
```json
{"job_id":"<uuid>","status":"queued","poll_url":"/v1/jobs/<uuid>"}
```
Poll every couple of seconds. Include both the `client_id` query parameter and `x-client-secret` header:
```sh
curl -sS "$BASE_URL/v1/jobs/<uuid>?client_id=booth-01" \
-H 'x-client-secret: replace-with-a-client-secret'
```
While running:
```json
{"job_id":"<uuid>","status":"processing","created_at":1234567890.0,"updated_at":1234567891.0}
```
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.
Recognized `params` include:
- `aspect_ratio`, `image_size`
- Google: `temperature`, `topP`, `seed`, `responseModalities`, `safetySettings`, `thinking`, `generationConfig`
- OpenRouter: `temperature`, `seed`, `reasoning` (object), `modalities`, `extra_body`
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.
## Management endpoints
| Method and path | Auth | Behavior |
| --- | --- | --- |
| `GET /healthz` | None | Liveness response `{"status":"ok"}`. |
| `GET /ui` | Network access control | Basic HTML administration page. |
| `GET /api/admin/state` | `x-admin-secret` | Lists clients and keys with only a short key preview. |
| `GET /api/admin/generations?limit=100` | `x-admin-secret` | Returns aggregate totals, per-client totals, and recent generation timing/cost records (limit 1–200). |
| `POST /api/admin/client` | `x-admin-secret` | Form fields: `client_id`, `name`, and required `client_secret` (16–256 characters); creates or updates a client. |
| `POST /api/keys` | `auth-secret` | Form fields: `client_id`, `provider`, `api_key`, optional `priority`. Admin or owning client may add keys. |
| `DELETE /api/keys/{key_id}` | `auth-secret` | Deletes a key owned by the authenticated admin/client. |
## HTTP errors
Validation and authorization failures use FastAPI's `{"detail":"..."}` response shape. Common statuses:
| Status | Meaning |
| --- | --- |
| `400` | Invalid parameters, provider, or missing provider keys. |
| `401` | Missing or invalid admin/client secret. |
| `403` | Client is unknown, inactive, or has no secret configured. |
| `404` | Client/key/job was not found, or the job belongs to another client. |
| `413` | Request or image upload exceeds configured limits. |
| `415` | Unsupported, malformed, or MIME-mismatched image. |
| `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.