fal

SkillCloud & infra

Use when calling a fal.ai endpoint by id to generate image, audio, or video from JS/Python/curl: subscribe vs submit, queue states, ED25519 webhook signature verification, per-call cost, or migrating off @fal-ai/serverless-client. NOT which model or art direction (that is ai-media); NOT the same models on another platform (that is replicate).

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the fal skill

What this skill tells your AI

The instructions your AI receives, as published by ericrisco/rsc-harness in skills/fal/SKILL.md and read by ahel’s review.

The wire to fal.ai's fast, pre-warmed media endpoints: call a model by id, control the queue, get the file back. fal is the fast-media path — latency-optimized image (FLUX, Seedream, SD), audio (TTS, music), and video (Veo, Wan, Kling, Hailuo) endpoints you invoke by id with FAL_KEY.

You own the mechanics: auth, call mode, queue states, webhook signatures, file I/O, per-call cost.

When NOT to use

You want…Go to
Which model / what to generate / art direction / multi-provider media pipelineai-media
The same kind of models on Replicate (replicate.run / predictions)replicate — images-specifically replicate-images
Renting a raw GPU pod you SSH into to train or custom-serverunpod
Deploying your own Python function as an autoscaling endpointmodal
Cheap hosted LLM text/chat completionstogether-fireworks
The generic provider-agnostic webhook receiver/verifier patternwebhooks

Rule: if you are not invoking a fal endpoint id with FAL_KEY, you are in the wrong skill.

Setup & auth

# JS — current client. NOT @fal-ai/serverless-client (deprecated, migrate).
npm i @fal-ai/client          # latest 1.10.1

# Python
pip install fal-client
export FAL_KEY="key_id:key_secret"
import { fal } from "@fal-ai/client";
// Reads FAL_KEY from env automatically; or set it explicitly:
fal.config({ credentials: process.env.FAL_KEY });

Rule: never ship FAL_KEY to a browser bundle. Proxy every call through your own server. Why: a key in client-side JS lets anyone drain your account — fal endpoints bill per call with no per-request cap.

Pick a call mode

All three modes hit the same queue. Choose by how long the job runs and where you call it from.

SituationModeWhy
Need the result now, can block, single short job (image, short TTS)subscribeSubmits + auto-polls until done; feels synchronous, no polling code
Long job (video), batch, or running in a serverless/edge handler that can't hold a connectionsubmit + webhook_url (or poll)Returns a request_id instantly; result arrives later, no held connection
Trivially short call, you accept no queue controlrunDirect synchronous call — no status, no logs; drops on long jobs
// Bad: run() on a 60s video — connection can drop, no retry, no progress.
const res = await fal.run("fal-ai/veo3", { input });

// Good: submit + webhook for anything that takes more than a few seconds.
const { request_id } = await fal.queue.submit("fal-ai/veo3", {
  input,
  webhookUrl: "https://api.example.com/fal/webhook",
});

subscribe — block and stream progress

const result = await fal.subscribe("fal-ai/flux/dev", {
  input: { prompt: "a red bicycle on a wet street, cinematic" },
  logs: true,
  onQueueUpdate: (update) => {
    if (update.status === "IN_PROGRESS") {
      update.logs?.forEach((l) => console.log(l.message)); // stream to user
    }
  },
});
console.log(result.data.images[0].url); // hosted output URL
import fal_client

def on_update(update):
    if isinstance(update, fal_client.InProgress):
        for log in update.logs:
            print(log["message"])

result = fal_client.subscribe(
    "fal-ai/flux/dev",
    arguments={"prompt": "a red bicycle on a wet street, cinematic"},
    with_logs=True,
    on_queue_update=on_update,
)
print(result["images"][0]["url"])

Python has an async twin for every method — subscribe_async, submit_async, run_async. Use them inside an event loop.

submit + queue polling

When you cannot or will not block, submit and poll the queue yourself.

const { request_id } = await fal.queue.submit("fal-ai/flux/dev", { input });

// Poll. Status moves IN_QUEUE -> IN_PROGRESS -> COMPLETED.
const status = await fal.queue.status("fal-ai/flux/dev", {
  requestId: request_id,
  logs: true,
});

// Once COMPLETED, fetch the result.
const result = await fal.queue.result("fal-ai/flux/dev", { requestId: request_id });
console.log(result.data.images[0].url);

Rule: back off between polls — start at ~1s, grow to a few seconds. Why: a tight while loop polling queue.status hammers the API and gains nothing; the job finishes when it finishes. For anything long-running, prefer a webhook over any polling at all.

Webhooks

Pass webhook_url (camelCase webhookUrl in the JS client) on submit; fal POSTs the result when the job finishes.

// Success
{ "request_id": "...", "gateway_request_id": "...", "status": "OK", "payload": { /* result */ } }
// Failure
{ "request_id": "...", "status": "ERROR", "error": "..." }
// Result couldn't be serialized
{ "request_id": "...", "status": "OK", "payload": null, "payload_error": "..." }

Delivery facts you must design for:

  • The initial POST has a 15-second timeout. On timeout or non-2xx, fal retries up to 10 times over ~2 hours.
  • Therefore your handler must be idempotent — the same request_id can arrive more than once. Dedupe on request_id (e.g. an upsert keyed on it) before acting.
  • Verify the ED25519 signature before trusting the body — four X-Fal-Webhook-* headers + a JWKS fetched from https://rest.fal.ai/.well-known/jwks.json. Why: an unverified webhook endpoint is a public write to your DB / spend trigger.

The full verification (header parsing, JWKS caching, ±5-minute timestamp check, message construction, per-key verify) and a complete idempotent handler in Node and Python live in references/queue-and-webhooks.md.

File inputs and outputs

Upload a local file to get a URL, then pass that URL into input for image-to-X jobs. Outputs always come back as hosted URLs.

const url = await fal.storage.upload(file); // File/Blob -> hosted URL
const out = await fal.subscribe("fal-ai/flux/dev/image-to-image", {
  input: { image_url: url, prompt: "make it snow" },
});
url = fal_client.upload_file("input.png")
out = fal_client.subscribe(
    "fal-ai/flux/dev/image-to-image",
    arguments={"image_url": url, "prompt": "make it snow"},
)

Cost control

Pricing is pay-per-use, per-model unit — never flat. The unit differs by model, so always read the model's pricing tab before you ship a loop.

UnitUsed by2026 example
Per imageimage diffusionSeedream V4 ~$0.03/image
Per second of outputvideoWan 2.5 ~$0.05/s; Veo 3 ~$0.4/s
Per megapixelsome image modelsvaries — read the tab
GPU-hourfal-served computeA100 40GB $0.99/h, H100 80GB $1.89/h (2026-05-13)

Spend knobs, by modality:

  • Image: lower num_inference_steps, drop resolution / megapixels, cut num_images.
  • Video: shorten duration, lower fps/resolution — per-second pricing scales linearly.
  • Batch: fal batch inference is 50% of serverless price — use it for offline bulk jobs where latency does not matter.

Worked estimate: 500 Seedream V4 images at ~$0.03 ≈ $15 serverless, ≈ $7.50 on the batch path.

The full model-family map and per-modality knob list live in references/models-and-cost.md.

Anti-patterns

Anti-patternWhy it bitesDo instead
FAL_KEY in a browser bundleAnyone reads it and bills your accountProxy through your server
run for a 30–60s videoConnection drops, no retry, no progresssubmit + webhook_url
Webhook handler with no signature checkPublic write / spend trigger anyone can forgeVerify ED25519 against the JWKS
Non-idempotent webhook handler10 retries over 2h create duplicate side effectsDedupe on request_id
Ignoring the model's pricing unit"$0.05" is per-second, not per-video — surprise billRead the pricing tab; pick the right knob
Tight while loop on queue.statusHammers the API, gains nothingBack off, or use a webhook
@fal-ai/serverless-clientDeprecated; missing fixes and APIs@fal-ai/client (v1.10.1)

Signals

GitHub stars
82
Forks
3
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
fal
Source
github.com/ericrisco/rsc-harness