Developers
API reference
Async-first. POST, get a request id, poll or take the webhook.
The contract
A POST returns in under 200 ms with a request id and a poll url. It never waits for the model, whether that takes a second or ten minutes.
- 01
POST the slug and inputs. Get
request_idandpoll_url. - 02
Wait. Poll the url, take the webhook, or both.
- 03
Read outputs from
media.ezflows.io. Output kept 90 days.
Authentication
Send your key as a bearer token. It is shown once at creation, stored only as a hash and display prefix, and every request records which key spent the money.
Authorization: Bearer $EZFLOWS_API_KEYSubmit a generation
The body is whatever the model's schema declares; each model page lists its parameters, types and defaults.
curl -X POST https://api.ezflows.io/v1/models/gpt-image-2 \
-H "Authorization: Bearer $EZFLOWS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "a wide shot of a desert at dusk"}'{
"request_id": "req_8f2a41c9b70e4d15",
"status": "queued",
"poll_url": "https://api.ezflows.io/v1/requests/req_8f2a41c9b70e4d15",
"status_url": "https://api.ezflows.io/v1/requests/req_8f2a41c9b70e4d15"
}Poll for the result
The record carries status, cost and five timing columns, so you can tell our queue from the model.
curl https://api.ezflows.io/v1/requests/req_8f2a41c9b70e4d15 \
-H "Authorization: Bearer $EZFLOWS_API_KEY"- submitted_at
- queue_started_at
- execution_started_at
- first_byte_at
- finished_at
Webhooks
Create an endpoint under Webhooks in the dashboard; the signing secret is shown once. Every delivery is an HTTP POST, at least once.
{
"id": "82cf46a4-841e-41d6-b3c7-facc937d3252",
"type": "request.succeeded",
"created_at": "2026-09-12T18:07:55Z",
"data": {
"id": "req_8f2a41c9b70e4d15",
"model_slug": "gpt-image-2",
"status": "succeeded",
"cost": 0.009,
"output": { "artifacts": [ { "url": "https://media.ezflows.io/…" } ] }
}
}- Events
request.succeededrequest.failedrequest.cancelled- Headers
ezflows-event-iddedupe on itezflows-timestampunix secondsezflows-signaturet=<ts>,v1=<hex HMAC-SHA256 of "<ts>.<raw body>">
- Verify
- Over the exact bytes received, in constant time; reject anything older than five minutes.
- Respond
- Any 2xx within 10 s. Redirects are not followed.
- Retries
- Five, with backoff: 30 s · 60 s · 2 min · 4 min · 8 min. Disabled after five consecutive dead deliveries; redeliver from the dashboard.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody: string, headers: Headers, secret: string): boolean {
const ts = headers.get("ezflows-timestamp") ?? "";
const sig = headers.get("ezflows-signature") ?? ""; // "t=<ts>,v1=<hex>"
const given = sig.split(",").find((p) => p.startsWith("v1="))?.slice(3) ?? "";
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // five-minute window
const expected = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}Errors
JSON with a stable code and a human message. The code is the contract; the message is for your logs.
{
"error": {
"code": "invalid_input",
"message": "Some parameters are invalid"
}
}| Status | Means | What to do |
|---|---|---|
| 401 | Missing or invalid key | Check the Authorization header. A revoked key stops working immediately. |
| 402 | Balance will not cover the run | Top up. Nothing was dispatched and nothing was charged. |
| 404 | Unknown model or request id | Model slugs are on each model page; request ids come from the POST. |
| 422 | The payload could not be priced or validated | The message names the field. An unpriceable payload is refused, never run for free. |
| 429 | Too many in flight for this account | Retry with backoff. Concurrency rises with spend over the last four weeks. |
| 5xx | Our fault | Retry with backoff. A generation that never ran is never billed. |
Flows
A published canvas becomes an endpoint that runs its snapshot with your inputs (schema at GET /v1/flows/<slug>). It answers 202 with a run_id, polled the same way; only the owner's key can run it for now.

curl -X POST https://api.ezflows.io/v1/flows/product-shot/runs \
-H "Authorization: Bearer $EZFLOWS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4f1c0b9e" \
-d '{"inputs": {"prompt": "a ceramic mug on linen"}}'
# 202 Accepted
# {"data": {"run_id": "6b0e…", "status": "running",
# "poll_url": "https://api.ezflows.io/v1/flows/product-shot/runs/6b0e…"}}