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.

01

POST the model slug and your inputs. You get back request_id and poll_url.

02

Wait — poll the url, or register a webhook and be told. Polling and webhooks are not exclusive.

03

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.

bash
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:

json
{
  "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

bash
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:

json
{
  "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:

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

StatusMeansWhat to do
401Missing or invalid keyCheck the Authorization header. A revoked key stops working immediately.
402Balance will not cover the runTop up. Nothing was dispatched and nothing was charged.
404Unknown model or request idModel slugs are on each model page; request ids are returned by the POST.
422The payload could not be priced or validatedThe message names the field. An unpriceable payload is refused, never run for free.
429Too many in flight for this accountRetry with backoff. Concurrency rises with spend over the last four weeks.
5xxOur faultRetry with backoff. A generation that never ran is never billed.