9.3 KiB
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-secretto administrator endpoints, orauth-secretto provider-key endpoints. - Client secret: configured when creating a client. Send it as
x-client-secretwhen generating or polling. It can also be used asauth-secretfor 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:
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:
{"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.
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.
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):
{
"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:
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):
{"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:
curl -sS "$BASE_URL/v1/jobs/<uuid>?client_id=booth-01" \
-H 'x-client-secret: replace-with-a-client-secret'
While running:
{"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
failedwithNo 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.