@apicity/fal
v0.1.0
Published
Fal Platform API provider for model management, pricing, usage, and analytics.
Maintainers
Readme
@apicity/fal
Fal Platform API provider for model management, pricing, usage, and analytics.
Installation
npm install @apicity/fal
# or
pnpm add @apicity/falQuick Start
import { fal as createFal } from "@apicity/fal";
const fal = createFal({ apiKey: process.env.FAL_API_KEY! });Real-world example: upload a portrait, render a Sora 2 video
fal's signature flow is upload-once, reuse-everywhere — drop bytes onto
fal's CDN via a presigned PUT, then thread the resulting
https://*.fal.media/ URL through any model endpoint. The two-step
snippet below combines
tests/integration/fal-storage-upload-initiate.test.ts
(POST initiate → PUT bytes) with
tests/integration/fal-sora-2-image-to-video.test.ts
(image-to-video generation), so every URL, byte count, and asset id
below comes from real recorded HARs.
import { readFile } from "node:fs/promises";
import { fal as createFal } from "@apicity/fal";
const fal = createFal({ apiKey: process.env.FAL_API_KEY! });
// 1. Reserve a signed upload slot. `initiate` returns two URLs: a
// permanent `file_url` you'll feed to downstream models, and a
// presigned `upload_url` you PUT the bytes to. Both point at the
// same fal CDN — no third-party hosting needed.
const slot = await fal.storage.upload.initiate({
file_name: "man.jpg",
content_type: "image/jpeg",
});
console.log(slot.file_url);
// → "https://v3b.fal.media/files/b/0a96d564/QR9a1l-E0UuoR6zOHUMlX_man.jpg"
// (the `cat1.jpg` recording shows the same URL shape with a
// cat1 suffix; the suffix tracks `file_name` you passed in.)
// 2. PUT the bytes to the presigned URL. fal storage is plain HTTP —
// no SDK call needed, just `fetch` with a matching Content-Type.
// The signature on `upload_url` expires after a short window;
// upload promptly. The resulting `file_url` is durable and
// fetchable by every fal model endpoint.
const bytes = await readFile("./man.jpg");
const put = await fetch(slot.upload_url, {
method: "PUT",
headers: { "Content-Type": "image/jpeg" },
body: bytes,
});
if (!put.ok) throw new Error(`upload failed: ${put.status}`);
// 3. Hand the now-permanent `file_url` to OpenAI's Sora 2 image-to-
// video model. fal returns a typed bundle: the MP4, a webp
// thumbnail, and a horizontal spritesheet — all hosted on the
// same fal CDN. `duration` accepts 4 | 8 | 12 | 16 | 20 (seconds);
// `aspect_ratio` is "auto" | "9:16" | "16:9".
const result = await fal.sora2.imageToVideo({
prompt: "the man waves at the camera as the wind blows his hair",
image_url: slot.file_url,
aspect_ratio: "16:9",
duration: 4,
});
console.log(result.video.url);
// → "https://v3b.fal.media/files/b/0a96bf3c/8U5wwkg9EC_eK0Jr3XyiR_Vgq1ZZPm.mp4"
console.log(result.video.file_size);
// → 2009236 // ~2 MB MP4 for a 4-second 720p clip
console.log(result.video_id);
// → "video_69e37804033c8191959194ea8aa8fc6e08bf9f3eb453b1b1"
console.log(result.thumbnail?.url);
// → "https://v3b.fal.media/files/b/0a96bf3c/bsgsaBd5IqdwOuufu_qSx_2yOP4u34.webp"
console.log(result.spritesheet?.url);
// → "https://v3b.fal.media/files/b/0a96bf3c/_9tqG1dEuRCEeegOulGrk_pWsHbiNB.bin"Notes
- The recorded sora-2 HAR inlines the image as a
data:image/jpeg;base64,…URL — fal accepts both inline data URLs and anyhttps://URL it can reach. Uploading via fal storage first keeps request bodies tiny (350 KB → <1 KB) and lets you reuse the asset across multiple model calls without re-encoding. - The package re-exports a one-call
uploadFile(provider, { data, filename, contentType })helper that wraps the initiate-then-PUT dance and returns thefile_urldirectly — use it when you don't need granular control over the lifecycle or signed URL. - Every POST endpoint exposes a Zod schema: call
fal.sora2.imageToVideo.schema.safeParse(input)to validate a payload before paying for inference. - Long-running calls accept an
AbortSignalsecond argument and compose with the package's middleware, e.g.withRetry(fal.sora2.imageToVideo, { retries: 3 })from@apicity/falto ride out transient queue / 429s. - Errors throw
FalErrorwithstatus,type,request_id, and the parsedbodyattached:try { ... } catch (e) { if (e instanceof FalError) console.error(e.status, e.body); }.
API Reference
68 endpoints across 18 groups. Each method mirrors an upstream URL path.
bytedance
POST https://api.fal.ai/v1/bytedance/seedance-2.0/fast/image-to-video
const res = await fal.bytedance.seedance2p0.fast.imageToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/bytedance/seedance-2.0/fast/reference-to-video
const res = await fal.bytedance.seedance2p0.fast.referenceToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/bytedance/seedance-2.0/fast/text-to-video
const res = await fal.bytedance.seedance2p0.fast.textToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/bytedance/seedance-2.0/image-to-video
const res = await fal.bytedance.seedance2p0.imageToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/bytedance/seedance-2.0/reference-to-video
const res = await fal.bytedance.seedance2p0.referenceToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/bytedance/seedance-2.0/text-to-video
const res = await fal.bytedance.seedance2p0.textToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/bytedance/seedream/v5/lite/edit
const res = await fal.bytedance.seedream.v5.lite.edit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/bytedance/seedream/v5/lite/text-to-image
const res = await fal.bytedance.seedream.v5.lite.textToImage({ /* ... */ });Source: packages/provider/fal/src/fal.ts
falAi
POST https://api.fal.ai/v1/fal-ai/elevenlabs/speech-to-text/scribe-v2
const res = await fal.falAi.elevenlabs.speechToText.scribeV2({ /* ... */ });Source: packages/provider/fal/src/fal.ts
gptImage1p5
POST https://api.fal.ai/v1/fal-ai/gpt-image-1.5
const res = await fal.gptImage1p5({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/gpt-image-1.5/edit
const res = await fal.gptImage1p5.edit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
hunyuan
POST https://api.fal.ai/v1/fal-ai/hunyuan-image/v3/instruct/edit
const res = await fal.hunyuan.v3.instructEdit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
klingVideo
POST https://api.fal.ai/v1/fal-ai/kling-video/o3/4k/image-to-video
const res = await fal.klingVideo.o3p4k.imageToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/kling-video/o3/4k/reference-to-video
const res = await fal.klingVideo.o3p4k.referenceToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/kling-video/o3/4k/text-to-video
const res = await fal.klingVideo.o3p4k.textToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/kling-video/v3/pro/image-to-video
const res = await fal.klingVideo.v3.pro.imageToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/kling-video/v3/pro/text-to-video
const res = await fal.klingVideo.v3.pro.textToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/kling-video/v3/standard/image-to-video
const res = await fal.klingVideo.v3.standard.imageToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/kling-video/v3/standard/text-to-video
const res = await fal.klingVideo.v3.standard.textToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
models
DELETE https://api.fal.ai/v1/models/requests/{param}/payloads
const res = await fal.v1.models.requests.payloads({ /* ... */ });Source: packages/provider/fal/src/fal.ts
GET https://api.fal.ai/v1/models
const res = await fal.v1.models({ /* ... */ });Source: packages/provider/fal/src/fal.ts
GET https://api.fal.ai/v1/models/pricing
const res = await fal.v1.models.pricing({ /* ... */ });Source: packages/provider/fal/src/fal.ts
GET https://api.fal.ai/v1/models/pricing/estimate
const res = await fal.v1.models.pricing.estimate({ /* ... */ });Source: packages/provider/fal/src/fal.ts
GET https://api.fal.ai/v1/models/requests/{param}/payloads
const res = await fal.v1.models.requests.payloads({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/models/pricing/estimate
const res = await fal.v1.models.pricing.estimate({ /* ... */ });Source: packages/provider/fal/src/fal.ts
GET https://api.fal.ai/v1/models
const res = await fal.v1.models({ /* ... */ });Source: packages/provider/fal/src/fal.ts
GET https://api.fal.ai/v1/models/pricing
const res = await fal.v1.models.pricing({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/models/pricing/estimate
const res = await fal.v1.models.pricing.estimate({ /* ... */ });Source: packages/provider/fal/src/fal.ts
DELETE https://api.fal.ai/v1/models/requests/{param}/payloads
const res = await fal.v1.models.requests.payloads({ /* ... */ });Source: packages/provider/fal/src/fal.ts
nanoBanana
POST https://api.fal.ai/v1/fal-ai/nano-banana/edit
const res = await fal.nanoBanana.edit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/nano-banana
const res = await fal.nanoBanana.textToImage({ /* ... */ });Source: packages/provider/fal/src/fal.ts
nanoBanana2
POST https://api.fal.ai/v1/fal-ai/nano-banana-2/edit
const res = await fal.nanoBanana2.edit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/nano-banana-2
const res = await fal.nanoBanana2.textToImage({ /* ... */ });Source: packages/provider/fal/src/fal.ts
nanoBananaPro
POST https://api.fal.ai/v1/fal-ai/nano-banana-pro/edit
const res = await fal.nanoBananaPro.edit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/nano-banana-pro
const res = await fal.nanoBananaPro.textToImage({ /* ... */ });Source: packages/provider/fal/src/fal.ts
queue
POST https://api.fal.ai/v1/POST
const res = await fal.v1.queue.submit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/POST
const res = await fal.v1.queue.submit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
qwenImage
POST https://api.fal.ai/v1/fal-ai/qwen-image
const res = await fal.qwenImage({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/qwen-image-edit
const res = await fal.qwenImage.edit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
serverless
POST https://api.fal.ai/v1/serverless/logs/stream
const res = await fal.v1.serverless.logs({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/serverless/files/file/local/{param}
const res = await fal.v1.serverless.files.uploadLocal({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/serverless/files/file/url/{param}
const res = await fal.v1.serverless.files.uploadUrl({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/serverless/files/file/local/{param}
const res = await fal.v1.serverless.files.uploadLocal({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/serverless/files/file/url/{param}
const res = await fal.v1.serverless.files.uploadUrl({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/serverless/logs/stream
const res = await fal.v1.serverless.logs({ /* ... */ });Source: packages/provider/fal/src/fal.ts
sora2
POST https://api.fal.ai/v1/fal-ai/sora-2/image-to-video
const res = await fal.sora2.imageToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/sora-2/text-to-video
const res = await fal.sora2.textToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
storage
const res = await fal.storage.upload.completeMultipart({ /* ... */ });Source: packages/provider/fal/src/fal.ts
const res = await fal.storage.upload.initiate({ /* ... */ });Source: packages/provider/fal/src/fal.ts
const res = await fal.storage.upload.initiateMultipart({ /* ... */ });Source: packages/provider/fal/src/fal.ts
veo3p1
POST https://api.fal.ai/v1/fal-ai/veo3.1/image-to-video
const res = await fal.veo3p1.imageToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/veo3.1
const res = await fal.veo3p1.textToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
wan
POST https://api.fal.ai/v1/fal-ai/wan/v2.7/edit
const res = await fal.wan.v2p7.edit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/wan/v2.7/edit-video
const res = await fal.wan.v2p7.editVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/wan/v2.7/image-to-video
const res = await fal.wan.v2p7.imageToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/wan/v2.7/pro/edit
const res = await fal.wan.v2p7.pro.edit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/wan/v2.7/pro/text-to-image
const res = await fal.wan.v2p7.pro.textToImage({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/wan/v2.7/reference-to-video
const res = await fal.wan.v2p7.referenceToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/wan/v2.7/text-to-image
const res = await fal.wan.v2p7.textToImage({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/fal-ai/wan/v2.7/text-to-video
const res = await fal.wan.v2p7.textToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
workflows
GET https://api.fal.ai/v1/workflows
const res = await fal.v1.workflows({ /* ... */ });Source: packages/provider/fal/src/fal.ts
GET https://api.fal.ai/v1/workflows
const res = await fal.v1.workflows({ /* ... */ });Source: packages/provider/fal/src/fal.ts
xai
POST https://api.fal.ai/v1/xai/grok-imagine-image
const res = await fal.xai.grokImagineImage({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/xai/grok-imagine-image/edit
const res = await fal.xai.grokImagineImage.edit({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/xai/grok-imagine-video/edit-video
const res = await fal.xai.grokImagineVideo.editVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/xai/grok-imagine-video/extend-video
const res = await fal.xai.grokImagineVideo.extendVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/xai/grok-imagine-video/image-to-video
const res = await fal.xai.grokImagineVideo.imageToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
POST https://api.fal.ai/v1/xai/grok-imagine-video/reference-to-video
const res = await fal.xai.grokImagineVideo.referenceToVideo({ /* ... */ });Source: packages/provider/fal/src/fal.ts
Middleware
import { fal as createFal, withRetry } from "@apicity/fal";
const fal = createFal({ apiKey: process.env.FAL_API_KEY! });
const models = withRetry(fal.get.v1.models, { retries: 3 });Part of the apicity monorepo.
License
MIT — see LICENSE.
