Fal SDK through AI Pass
Use the official @fal-ai/client with AI Pass as its authenticated queue proxy. You keep Fal's endpoint IDs, input objects, queue methods, and result shapes; AI Pass supplies the Fal account, charges the caller's AI Pass wallet, and settles the request from Fal's billing evidence.
You do not need a Fal account or a Fal API key.
This integration covers Fal's queue workflow:
subscribe,queue.submit,queue.status,queue.result, andqueue.cancel. It does not proxy synchronousrun, streaming, realtime, webhooks, or Fal storage uploads.
Choose the right authentication setup
| Where the code runs | AI Pass credential | Fal client configuration |
|---|---|---|
| Node.js, Bun, a serverless function, or another trusted backend | AI Pass API key | Set credentials and force the proxy with when: "always" |
| Browser application | AI Pass OAuth token | Initialize the AI Pass Web SDK and pass AiPass.falConfig() |
Never put an AI Pass API key in browser code. Browser apps should use AI Pass OAuth so every user authorizes the app and pays from their own wallet.
Server-side setup with an AI Pass API key
1. Install the official Fal client
npm install @fal-ai/client
AI Pass tests the proxy against @fal-ai/client 1.10.1.
2. Store your AI Pass key in the environment
export AIPASS_API_KEY="your-ai-pass-api-key"
Create or manage API keys in the AI Pass Developer Dashboard.
3. Create the Fal client
import { createFalClient } from "@fal-ai/client";
const apiKey = process.env.AIPASS_API_KEY;
if (!apiKey) {
throw new Error("AIPASS_API_KEY is required");
}
export const fal = createFalClient({
credentials: apiKey,
proxyUrl: {
url: "https://aipass.one/v1/fal/proxy",
when: "always"
}
});
The object form of proxyUrl matters on a backend. A plain string uses the proxy only in browser runtimes by default; when: "always" guarantees that Node.js and other server runtimes also send the request through AI Pass.
Do not set FAL_KEY. The credential in this configuration is an AI Pass API key, not a Fal key.
Browser setup with AI Pass OAuth
The browser flow uses both SDKs:
<script src="https://aipass.one/aipass-sdk.js"></script>
<script type="module">
import { createFalClient } from "https://esm.sh/@fal-ai/[email protected]";
AiPass.initialize({
clientId: "your_ai_pass_oauth_client_id",
requireLogin: true
});
const fal = createFalClient(AiPass.falConfig());
const result = await fal.subscribe("fal-ai/flux/schnell", {
input: {
prompt: "A quiet Mediterranean street at golden hour",
image_size: "landscape_16_9",
num_images: 1
}
});
console.log(result.data.images[0].url);
</script>
AiPass.falConfig():
- points the Fal client at
https://aipass.one/v1/fal/proxy; - obtains an AI Pass OAuth token when needed;
- attaches the current token and OAuth client binding to every request; and
- uses a refreshed token for later status, result, and cancel calls.
Do not pass credentials in the browser. Do not embed an API key in JavaScript, an environment variable exposed by a frontend build, or a public repository.
See the AI Pass Web SDK guide for OAuth client setup and login behavior.
Call a model with subscribe
subscribe is the simplest and recommended flow. It submits the job, polls its status, and returns the result after completion.
const endpoint = "fal-ai/flux/schnell";
const result = await fal.subscribe(endpoint, {
input: {
prompt: "A glass observatory above the clouds, editorial photography",
image_size: "landscape_16_9",
num_images: 1
},
mode: "polling",
pollInterval: 1000,
timeout: 180_000,
onQueueUpdate(update) {
console.log("Fal status:", update.status);
}
});
console.log("Request:", result.requestId);
console.log("Image:", result.data.images[0].url);
Possible queue states are IN_QUEUE, IN_PROGRESS, and COMPLETED. The result payload under result.data is Fal's original model-specific output document. For example, an image model commonly returns data.images, while a video model may return data.video.
AI Pass does not manufacture Fal runtime logs, queue positions, or metrics. Use onQueueUpdate for lifecycle state, not as a guaranteed log stream.
Choose the model and parameters
Browse fal.ai/models, open a model, and copy its exact endpoint ID. AI Pass does not translate that ID.
The model's Fal page is also the source of truth for its input fields, accepted values, defaults, and output shape. Place those fields inside input exactly as Fal documents them:
await fal.subscribe("exact/fal-endpoint-id", {
input: {
// The model's documented Fal input object goes here.
}
});
There is no universal AI Pass parameter list for Fal models.
How text-to-image and text-to-video are selected
The endpoint ID selects the operation. There is no separate type parameter.
- A text-to-image endpoint receives a text-to-image input object.
- A text-to-video endpoint receives a text-to-video input object.
- An image-to-video endpoint normally requires an
image_urlin addition to its motion prompt. - Some endpoint IDs end in
/text-to-imageor/text-to-video; others, such asfal-ai/veo3.1, express the capability through the model itself.
Always copy the precise endpoint variant from Fal. Do not send type: "video" or try to turn an image endpoint into a video endpoint with a body field.
Text-to-image example with quality
const result = await fal.subscribe("fal-ai/gpt-image-1/text-to-image", {
input: {
prompt: "A premium product photograph of a silver mechanical watch",
image_size: "1024x1024",
quality: "high",
num_images: 1,
output_format: "png",
background: "auto"
}
});
console.log(result.data.images[0].url);
quality is a model input, not a Fal SDK option. Use it only when the chosen model's Fal input schema declares it. Other models may express quality through resolution, image_size, inference steps, or a different endpoint variant.
Text-to-video example with duration
const result = await fal.subscribe("fal-ai/veo3.1", {
input: {
prompt: "A slow aerial move over a volcanic island at sunrise",
duration: "8s",
resolution: "720p",
aspect_ratio: "16:9",
generate_audio: true
},
mode: "polling",
pollInterval: 2000,
timeout: 10 * 60_000,
onQueueUpdate(update) {
console.log(update.status);
}
});
console.log(result.data.video.url);
duration is also a model input. Its type and allowed vocabulary vary by endpoint:
- one model may accept a suffixed string such as
"8s"; - another may accept a string such as
"8"; - another may require the number
8; and - some models define length through
num_framesandfpsinstead.
Do not normalize these values yourself. Send the exact form documented on the selected Fal model page.
Image-to-video inputs
Use a reachable HTTPS URL when the endpoint needs an image:
const result = await fal.subscribe("your/image-to-video-endpoint", {
input: {
prompt: "The camera moves slowly toward the subject",
image_url: "https://cdn.example.com/input.jpg",
// Add the endpoint's documented duration, resolution, and other fields.
}
});
Do not pass a browser File or Blob. The Fal client handles those by calling Fal's storage API, and AI Pass does not proxy that API. Upload the file to a location Fal can fetch over HTTPS, then pass its URL. Generated Fal media URLs can expire, so save or download important results promptly.
Run the queue lifecycle manually
Use the queue methods when you need to return a request ID immediately, persist it, poll from another worker, or provide a cancel button.
Submit
const endpoint = "fal-ai/flux/schnell";
const queued = await fal.queue.submit(endpoint, {
input: {
prompt: "A small cabin beside a frozen lake",
image_size: "landscape_16_9"
}
});
const requestId = queued.request_id;
console.log(requestId, queued.status);
Check status
const status = await fal.queue.status(endpoint, {
requestId,
logs: false
});
console.log(status.status);
Retrieve the result
Call result only after the status becomes COMPLETED:
const result = await fal.queue.result(endpoint, { requestId });
console.log(result.data);
Use the same endpoint ID and the same AI Pass credential that created the request. A different API key or a token from another OAuth app cannot retrieve or cancel it.
Cancel
await fal.queue.cancel(endpoint, { requestId });
Cancellation is best effort. A model that has already completed or reached a non-cancellable execution stage may still finish and may still incur provider cost.
Complete polling helper
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function runQueued(fal, endpoint, input) {
const queued = await fal.queue.submit(endpoint, { input });
const requestId = queued.request_id;
while (true) {
const status = await fal.queue.status(endpoint, { requestId });
if (status.status === "COMPLETED") {
return fal.queue.result(endpoint, { requestId });
}
await delay(1500);
}
}
If execution failed upstream, the status lifecycle finishes and the result call returns Fal's error rather than an empty success document.
Retry safety and idempotency
Fal normally creates a new stochastic generation for every submission. AI Pass preserves that behavior: two identical calls create two jobs.
If your own network retry must resolve to the first job instead of creating another one, supply an idempotency key:
const queued = await fal.queue.submit("fal-ai/flux/schnell", {
input: {
prompt: "A red paper boat on black water"
},
headers: {
"Idempotency-Key": "image-order-1842-attempt-1"
}
});
An AI Pass idempotency key must contain 8–128 letters, numbers, dots, underscores, colons, or hyphens. Reuse it only for a retry of the same logical request. Use a new key when the user deliberately asks for another generation.
Errors
The Fal client exposes ApiError and ValidationError. AI Pass returns Fal-compatible error documents, so existing Fal error handling continues to work.
import {
ApiError,
ValidationError,
createFalClient
} from "@fal-ai/client";
try {
const result = await fal.subscribe("fal-ai/veo3.1", {
input: {
prompt: "A lighthouse in a storm",
duration: "8s"
}
});
console.log(result.data);
} catch (error) {
if (error instanceof ValidationError) {
console.error("Invalid model input:", error.fieldErrors);
} else if (error instanceof ApiError) {
console.error("Request failed:", error.status, error.body);
} else {
console.error("Unexpected error:", error);
}
}
Common statuses:
| Status | Meaning |
|---|---|
401 | The AI Pass key or OAuth token is missing, invalid, or expired |
402 | The caller's AI Pass wallet cannot cover admission |
404 | The endpoint or request is unavailable, or a provider result has expired |
409 | The request is not in a state where that lifecycle action can run |
413 | The JSON request body exceeds the bridge limit |
422 | The model rejected an input field or value |
429 | A rate limit was reached |
502 | Fal or provider-cost evidence could not be reached or validated |
For 422, read the returned field errors and compare the request with the model's current Fal input schema. Do not log API keys, OAuth tokens, prompts, private media URLs, or full provider results in production error logs.
Model availability, holds, and final charges
The models shown in AI Pass's Available Models catalog are curated routes. That catalog is not an allowlist for the Fal SDK bridge.
Any syntactically valid public Fal queue endpoint can be called through /v1/fal, including an endpoint that has no AI Pass catalog row. Private Fal endpoints still require whatever upstream access they define and are not made public by AI Pass.
| Endpoint kind | Admission behavior |
|---|---|
| Curated by AI Pass | Uses the known model schema and a model-specific maximum wallet hold |
| Not yet curated | Uses an optimistic $0.01 admission hold, requires at least $0.01 available balance, and is limited to 20 submissions per caller per hour |
The $0.01 optimistic hold is not the price or a price cap. Once Fal publishes the exact request-level billing event, AI Pass settles the real provider cost plus the applicable AI Pass and app pricing. The final charge can be higher or lower than the initial hold. Unused reserved funds are released.
If an uncurated endpoint becomes part of a high-volume production workflow, contact AI Pass so it can be curated with a model-specific input and pricing contract.
Supported Fal client surface
| Fal client feature | Through AI Pass | Notes |
|---|---|---|
fal.subscribe() | Yes | Use polling mode |
fal.queue.submit() | Yes | Returns an AI Pass-backed Fal queue document |
fal.queue.status() | Yes | Lifecycle status; runtime logs and metrics are not guaranteed |
fal.queue.result() | Yes | Returns Fal's original output document |
fal.queue.cancel() | Yes | Best-effort cancellation |
fal.run() | No | Uses Fal's synchronous host rather than the queue bridge |
fal.stream() / streaming status | No | Uses protocols and paths outside the queue bridge |
fal.realtime | No | Uses WebSockets outside the queue bridge |
fal.storage.upload() | No | Upload files elsewhere and pass reachable HTTPS URLs |
| Webhooks | No | Poll status or use subscribe |
Migrate an existing Fal integration
If you already use fal.subscribe or the queue methods, the endpoint ID, input object, and result parsing can remain unchanged.
Before:
const fal = createFalClient({
credentials: process.env.FAL_KEY
});
After:
const fal = createFalClient({
credentials: process.env.AIPASS_API_KEY,
proxyUrl: {
url: "https://aipass.one/v1/fal/proxy",
when: "always"
}
});
You can then keep calls such as:
const result = await fal.subscribe("fal-ai/flux/schnell", {
input: {
prompt: "A geometric poster in cobalt and cream",
image_size: "portrait_4_3"
}
});
For a browser integration, remove the API key and your own Fal server proxy, initialize the AI Pass Web SDK, and use:
const fal = createFalClient(AiPass.falConfig());
Raw HTTP equivalent
The SDK proxy is recommended, but the same queue surface is available as ordinary HTTP. Replace Fal's queue origin with the AI Pass Fal base and keep the endpoint ID unchanged:
curl -X POST \
"https://aipass.one/v1/fal/fal-ai/flux/schnell" \
-H "Authorization: Key $AIPASS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A quiet harbor at blue hour",
"image_size": "landscape_16_9"
}'
The response contains request_id, status_url, response_url, and cancel_url. Send the same credential when following those URLs.
Production checklist
- Copy the exact endpoint ID and input schema from the model's current Fal page.
- Keep API keys on trusted servers; use
AiPass.falConfig()in browsers. - In Node.js or another non-browser runtime, set
proxyUrl.whento"always". - Use the default polling queue workflow, not
run, streaming, realtime, or webhooks. - Pass reachable HTTPS URLs instead of
FileorBlobinputs. - Treat
duration,quality,resolution, and similar fields as model-specific inputs. - Persist the request ID when using manual queue methods.
- Add an idempotency key only when retrying the same logical submission.
- Handle
402,422, and429explicitly. - Download important output media before its provider URL expires.