WOWFLOW API

Programmatic access to every AI image & video model. Same balance as your WOWFLOW account. Bearer-token auth, JSON in / JSON out, no SDK required.

API price = UI price + 5%. No subscription, no monthly minimum. Generations are billed against your existing WOWFLOW balance.

1. Authentication

All endpoints require an API key in the Authorization header:

Authorization: Bearer wf_live_...

Generate keys in Settings → API Access. Each account can have up to 10 active keys. The secret value is shown only once at creation time — store it somewhere safe.

2. Base URL

https://wowflow.space/api/v1

3. List available models

GET /api/v1/models
curl https://wowflow.space/api/v1/models \
  -H "Authorization: Bearer $WOWFLOW_KEY"
import os, requests

r = requests.get(
    "https://wowflow.space/api/v1/models",
    headers={"Authorization": f"Bearer {os.environ['WOWFLOW_KEY']}"},
)
print(r.json())
const r = await fetch("https://wowflow.space/api/v1/models", {
  headers: { Authorization: `Bearer ${process.env.WOWFLOW_KEY}` },
});
console.log(await r.json());

Response:

{
  "data": [
    {
      "id":           "nano-banana-pro-1k",
      "name":         "Nano Banana Pro 1K",
      "type":         "image",
      "price_cents":  11,
      "price_usd":    0.11,
      "resolution":   "1K",
      "duration_sec": null
    },
    ...
  ]
}

4. Create a generation

POST /api/v1/generations

Body:

FieldTypeRequiredDescription
model string yes id from /models
prompt string yes Text description. Seedream requires at least 3 characters.
input object no Model parameters — see 4.1 below
any other key any no Merged into input. Putting aspect_ratio beside prompt and putting it inside input are equivalent.
Reference images. Any of these spellings works, as a single URL string or an array, at the top level or inside input: image, images, image_url, image_urls, input_urls, reference_image, reference_images, refs. They are normalised to whichever field the underlying provider reads, so you never have to match its naming. Images must be publicly reachable over HTTPS.

{"model":"seedream-5-pro-edit-2k","prompt":"add a red hat","image":"https://.../base.png"}

4.1 Model parameters

Everything in this section goes in input (or at the top level — they are merged). Parameters that set the price are pinned by the model id and are ignored if you send them: quality, resolution, duration, sound, mode. Choose the tier by choosing the model id, not by sending a parameter.

ModelsReference inputOther parameters
seedream-5-pro-1k / -2k — (text to image) aspect_ratio: 1:1 4:3 3:4 16:9 9:16 2:3 3:2 21:9 (default 1:1)
output_format: png | jpeg
nsfw_checker: boolean
seedream-5-pro-edit-1k / -2k required, up to 10 images Same as above
gpt-image-2-1k / -2k / -4k optional — only the first image is used aspect_ratio: auto 1:1 3:2 2:3 16:9 9:16 4:3 3:4 21:9
size: explicit WIDTHxHEIGHT, overrides aspect_ratio. Both sides must divide by 16 and the ratio must sit between 1:3 and 3:1.
nano-banana-pro-1k / -2k / -4k optional, up to 8 images aspect_ratio: 1:1 3:2 2:3 16:9 9:16 4:3 3:4 21:9
kling-3-pro-*, kling-3-standard-* optional — first frame, or [first, last] Resolution, duration and audio are pinned by the model id
kling-motion-control-720p / -1080p required — one character image, plus a motion reference video in video / video_urls Both the image and the video are mandatory
seedance-2-* optional, up to 9 reference images first_frame_url, last_frame_url, reference_video_urls, reference_audio_urls
happyhorse-720p-10s — (text to video)
Unknown parameters are forwarded as-is to the provider, which mostly ignores them. If a provider rejects the request you get 502 upstream_error with its message attached, and your balance is refunded automatically — a rejected request never costs anything.
curl -X POST https://wowflow.space/api/v1/generations \
  -H "Authorization: Bearer $WOWFLOW_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model":  "nano-banana-pro-1k",
    "prompt": "a misty forest at dawn, soft light, cinematic"
  }'
import os, requests

r = requests.post(
    "https://wowflow.space/api/v1/generations",
    headers={
        "Authorization": f"Bearer {os.environ['WOWFLOW_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "model":  "nano-banana-pro-1k",
        "prompt": "a misty forest at dawn, soft light, cinematic",
    },
)
gen = r.json()
print(gen["id"], gen["status"])
const r = await fetch("https://wowflow.space/api/v1/generations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.WOWFLOW_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model:  "nano-banana-pro-1k",
    prompt: "a misty forest at dawn, soft light, cinematic",
  }),
});
const gen = await r.json();
console.log(gen.id, gen.status);

Response (201):

{
  "id":         "8c2e90b1-...",
  "status":     "processing",
  "model":      "nano-banana-pro-1k",
  "cost_cents": 11,
  "cost_usd":   0.11,
  "created_at": "2026-05-08T14:23:11.000Z"
}

5. Poll for the result

GET /api/v1/generations?id=<uuid>

Poll every 2–5 seconds until status is completed or failed. Webhooks are coming in v2.

curl "https://wowflow.space/api/v1/generations?id=$ID" \
  -H "Authorization: Bearer $WOWFLOW_KEY"
import time, os, requests

def wait_for(gen_id, timeout=300):
    headers = {"Authorization": f"Bearer {os.environ['WOWFLOW_KEY']}"}
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(
            f"https://wowflow.space/api/v1/generations?id={gen_id}",
            headers=headers,
        ).json()
        if r["status"] == "completed":
            return r["result_url"]
        if r["status"] == "failed":
            raise Exception(r.get("error") or "failed")
        time.sleep(3)
    raise TimeoutError("Generation timed out")
async function waitFor(id, timeoutMs = 300_000) {
  const headers = { Authorization: `Bearer ${process.env.WOWFLOW_KEY}` };
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const r = await fetch(
      `https://wowflow.space/api/v1/generations?id=${id}`,
      { headers }
    ).then(x => x.json());
    if (r.status === "completed") return r.result_url;
    if (r.status === "failed")    throw new Error(r.error || "failed");
    await new Promise(r => setTimeout(r, 3000));
  }
  throw new Error("Generation timed out");
}

Response when ready:

{
  "id":           "8c2e90b1-...",
  "status":       "completed",
  "model":        "nano-banana-pro-1k",
  "result_url":   "https://.../result.png",
  "cost_cents":   11,
  "cost_usd":     0.11,
  "created_at":   "2026-05-08T14:23:11.000Z",
  "completed_at": "2026-05-08T14:23:32.000Z"
}
Download the file as soon as it is ready. result_url is not guaranteed to be permanent: for the video models and Seedream it points at the provider's CDN, which deletes generated files after roughly two weeks. Fetch the bytes and store them on your side in the same job that polls for the result — do not treat the URL as durable storage.

6. List recent generations

GET /api/v1/generations?limit=50

Omit id to list your account's most recent generations (up to 200).

7. Check balance

GET /api/v1/balance
{
  "balance_cents":     1234,
  "balance_usd":       12.34,
  "total_topup_cents": 5000,
  "total_spent_cents": 3766,
  "currency":          "USD"
}

8. Transaction history

GET /api/v1/transactions?limit=50

Returns recent spend + top-up transactions, ordered newest-first.

9. Errors

All errors follow this shape:

{ "error": { "code": "insufficient_balance", "message": "..." } }
HTTPcodeMeaning
400 missing_model / missing_prompt / unknown_model Bad request
401 missing_api_key / invalid_api_key Auth failure
402 insufficient_balance Top up to continue
404 not_found Generation id doesn't belong to your account
502 upstream_error Provider rejected the request (you were refunded)

10. Versioning & stability

This is v1. Breaking changes will be released under a new path (/api/v2/...). Additive changes — new models, new fields — may happen at any time on v1.

11. Roadmap

Questions? Ping @fukfaku on Telegram.