Generation
Generation endpoints run a GPU workflow — an image or video pipeline — and return a job you poll or receive by webhook. Every endpoint has its own field names, types, defaults and bounds; the catalog below is generated live from the API, so it always matches what is deployed.
POST /api/v1/generate/{slug}
- Auth: Bearer key with the
generate:imagescope. - Requires: a positive credit balance (otherwise
402).
generate:image is on by defaultUnlike chat:companion, the generation scope is granted to every API key at creation. If you
have a key, you can call these endpoints — no extra provisioning step.
Request body
Every generation endpoint takes the same envelope. Only the contents of inputs differ, and those
are what the catalog below documents per endpoint.
| Field | Type | Required | Notes |
|---|---|---|---|
inputs | object | ✓ | The endpoint's own named inputs. Unknown keys are rejected. |
webhook_url | string | - | HTTPS URL notified when the job finishes. Falls back to your account-level webhook. The callback is signed — see Webhooks. |
Media inputs
An input typed as an image, video or audio URL is a URL Phantom Router fetches, not an upload.
It must be publicly reachable over HTTPS — we resolve and SSRF-validate it before the job is
created or billed, so private, loopback and link-local addresses are refused with 400.
Signed URLs work, but the signature is stripped before the URL is stored in job metadata.
Response
A successful call returns 202 Accepted immediately — the GPU work happens asynchronously:
{ "job_id": "6a0c2d1e-4f3b-4b1a-9f2c-8d7e6a5b4c30", "status": "queued" }
Then either poll or wait for the webhook:
GET /api/v1/generate/jobs/{job_id}
:::danger Verify the webhook signature
Every callback carries a Standard Webhooks signature. Verify it before you act on the payload —
otherwise anyone who learns your webhook URL can forge a completed job whose outputs point at a
host they control. See Webhooks.
:::
A completed job carries status, an outputs object keyed by the endpoint's published output
names, and whatever envelope fields that endpoint declares (created_at, completed_at, and
optionally execution time). See Jobs for the shared job-history endpoints.
Output URLs are presigned and expire — download the artifact rather than storing the link.
Example
curl -X POST https://api.phantomrouter.ai/api/v1/generate/{slug} \
-H "Authorization: Bearer $PHANTOM_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": { "prompt": "a lighthouse at dusk" }
}'
Each card in the catalog below carries a copyable command with that endpoint's real fields already filled in.
Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST | An input failed validation, an unknown key was sent, or a media URL is not fetchable. |
| 401 | UNAUTHORIZED | Missing or invalid key. |
| 402 | PAYMENT_REQUIRED | Insufficient credit balance. |
| 403 | FORBIDDEN | Key lacks generate:image. |
| 404 | NOT_FOUND | No such endpoint — or one your key cannot reach. See below. |
| 503 | SERVICE_UNAVAILABLE | The endpoint's GPU provider is not currently dispatchable. Retry with backoff. |
404 is not always a typoEndpoints can be restricted to keys holding an extra scope. A restricted endpoint returns exactly
the same 404 as one that does not exist, so that a private endpoint cannot be discovered by
probing. If an endpoint you were told about returns 404, ask for the scope rather than assuming
the slug is wrong — the catalog below lists only the endpoints open to every key.
API versions, deprecation and sunset
An endpoint's contract never changes underneath you. When we improve a workflow in a way that
would change the request or response shape, the change ships as a new endpoint at a new path
(for example character-image-v2), and the endpoint you already call keeps working unchanged.
Use schema_version to detect contract changes: it is a stable hash of the endpoint's published
schema, returned by GET /api/v1/generate/{slug}/schema and in the catalog JSON. It changes only
when the contract itself changes — purely additive improvements (a new optional input, a new
response field) do change it, but never invalidate an existing integration.
When an endpoint version is scheduled for retirement, its responses announce it with standard HTTP headers:
| Header | Meaning |
|---|---|
Deprecation | The endpoint is deprecated, as @<unix seconds> (RFC 9745). It still works. |
Sunset | The date after which it may stop working (RFC 8594). |
Link | rel="successor-version" — the path of the replacement endpoint (RFC 8288). |
The catalog JSON carries the same facts as deprecated, sunset and successor_slug keys on the
affected endpoint. Watch for the Deprecation header (or those keys) in your integration and plan
the move to the successor before the sunset date; after retirement the old path returns 404.
Catalog
Every published generation endpoint, with its exact request and response contract. Generated live from the API, so a newly published endpoint appears here without a docs deploy.
The same data is served as JSON at GET /api/v1/public/generate — unauthenticated, CORS-open, and
ETag-cached. Each endpoint is also available on its own at
GET /api/v1/public/generate/{slug}. The per-endpoint schema_version changes only when the
contract itself changes.
Loading the live endpoint catalogue from /api/v1/public/generate. It is also readable directly as raw catalogue JSON — no key required.