Files
ph_api/docs/API.md
T

9.3 KiB
Raw Blame History

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:

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 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.