Files
ph_api/docs/API.md
T

9.0 KiB
Raw Blame History

phproxy API

Base URL on this machine's Tailscale network: http://100.67.41.125:8080

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.

For the current local development server, the temporary admin secret is local-development-admin-secret. Replace it before using the service with real API keys or outside a trusted development tailnet.

Create a client:

BASE_URL=http://100.67.41.125:8080
ADMIN_SECRET=local-development-admin-secret

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.

Send x-client-secret if the client has a secret configured. The service accepts JPEG, PNG, WebP, and GIF images, up to 8 files and 50 MiB aggregate by default. Limits can be changed by the operator.

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, optional client_secret; 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 or inactive.
404 Client/key/job was not found, or the job belongs to another client.
413 Request or image upload exceeds configured limits.
415 Unsupported image MIME type.
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.