Developers
API documentation
Async-first. Every generation endpoint returns a request id immediately and never blocks. Poll it, or take the webhook.
The contract
A POST returns in under 200 ms with a request id and a poll url. It does not wait for the model, whether the model takes a second or ten minutes.
POST the model slug and your inputs. You get back request_id and poll_url.
Wait — poll the url, or register a webhook and be told. Polling and webhooks are not exclusive.
Read the result. Output files are served from media.ezflows.io and kept for 90 days.
Authentication
Pass your key as Authorization: Bearer ez_live_…. We store only a hash and a display prefix: a key is shown exactly once at creation and cannot be recovered. Every request records which key spent the money, so revoking one tells you what it was doing.
Submit 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"}'Responds immediately:
{
"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
curl https://api.ezflows.io/v1/requests/req_8f2a41c9b70e4d15 \
-H "Authorization: Bearer $EZFLOWS_API_KEY"The record carries status, cost and five timing columns, so you can tell whether a slow run was our queue or the model itself.
- submitted_at
- queue_started_at
- execution_started_at
- first_byte_at
- finished_at
Webhooks
Create an endpoint under Webhooks in the dashboard and pick the events (request.succeeded, request.failed, request.cancelled). The signing secret is shown once. Every delivery is an HTTP POST:
{
"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/…" } ] }
}
}Three headers carry identity and the signature: ezflows-event-id (dedupe on it — delivery is at least once), ezflows-timestamp (unix seconds) and ezflows-signature (t=<ts>,v1=<hex HMAC-SHA256 of "<ts>.<raw body>">). Verify over the exact bytes you received, compare in constant time, and reject anything older than five minutes:
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));
}Respond with any 2xx within 10 seconds. We do not follow redirects. A failing endpoint is retried five times with exponential backoff (30 s, 60 s, 2 min, 4 min, 8 min) and then disabled after five consecutive dead deliveries; you can redeliver from the dashboard.
Errors
JSON with a stable code and a human message. Treat the code as the contract; the message is for your logs.
| 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 are returned by 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. |