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.

  1. 01

    POST the slug and inputs. Get request_id and poll_url.

  2. 02

    Wait. Poll the url, take the webhook, or both.

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

httpEvery request
Authorization: Bearer $EZFLOWS_API_KEY

Submit a generation

The body is whatever the model's schema declares; each model page lists its parameters, types and defaults.

curlPOST /v1/models/:slug
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"}'
json202 · 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

The record carries status, cost and five timing columns, so you can tell our queue from the model.

curlGET /v1/requests/:id
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.

jsonDelivery body
{
  "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.succeeded request.failed request.cancelled
Headers
  • ezflows-event-id dedupe on it
  • ezflows-timestamp unix seconds
  • ezflows-signature t=<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.
typescriptverify.ts
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.

jsonEvery error
{
  "error": {
    "code": "invalid_input",
    "message": "Some parameters are invalid"
  }
}
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 come from 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.

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.

A canvas published as an endpoint with a form and an API consumer
curlPOST /v1/flows/:slug/runs
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…"}}