Loading...
Two things, from any backend, on the same engine the FlashFlow app runs on. Send raw footage and get back a finished, cut, captioned video with a receipt of every edit decision. Or generate TikTok-Shop-grade scripts — hooks, beats, CTAs, b-roll, overlays.
One endpoint. No auth header required on the free tier — IP-bucketed rate limit applies. Paid usage attaches a session cookie or anx-api-keyheader. Keys are live now — issue one from your account settings.
curl -X POST https://flashflowai.com/api/public/generate-script \
-H "Content-Type: application/json" \
-d '{
"product_name": "Matcha Energy Powder",
"product_description": "Plant-based caffeine, no jitters",
"platform": "tiktok",
"risk_tier": "BALANCED"
}'The same engine the app runs on. Send raw footage — retakes, rambling, dead air and all — and get back a cut, captioned, correctly-sized video, plus a receipt of every edit decision that was made. Three calls: get an upload URL, start a job, poll it.
Requires a paid plan. An API key on a free plan gets 402. Browser sessions are not affected — this gate applies to key-authenticated calls only.
Every request takes Authorization: Bearer ff_ak_… or x-api-key: ff_ak_…. Keys are shown once when created and stored only as a SHA-256 hash, so a lost key is replaced rather than recovered.
Files never pass through the API. You get a signed URL and PUT straight at storage, which is why a 400 MB video is not a problem. Max 2 GB per file.
# 1. Ask for a signed upload URL
curl -X POST https://flashflowai.com/api/create/upload-url \
-H "Authorization: Bearer $FLASHFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "filename": "take-1.mp4", "mime": "video/mp4", "size": 48210394 }'
# → { "ok": true, "signed_url": "...", "storage_path": "...",
# "public_url": "...", "backend": "r2" }
# 2. PUT the file straight at signed_url (no auth header on this one)
curl -X PUT "$SIGNED_URL" -H "Content-Type: video/mp4" --data-binary @take-1.mp4curl -X POST https://flashflowai.com/api/create/jobs \
-H "Authorization: Bearer $FLASHFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "post",
"storage_path": "<storage_path from step 1>",
"backend": "r2",
"source_url": "<public_url from step 1>",
"aspect_ratios": ["9:16"],
"caption_style": "karaoke",
"vibe": "real",
"describe": "cut it tight, remove the repeats"
}'
# → { "ok": true, "job_id": "3b4b0baa-caf6-4e93-85c7-fa01404327f2" }describe is plain English and is really read — “remove the repeats”, “keep it under 30 seconds”, “don’t cut where I’m showing the product”. Omit it and sensible defaults apply.
curl https://flashflowai.com/api/create/jobs/$JOB_ID \
-H "Authorization: Bearer $FLASHFLOW_API_KEY"
# → { "ok": true, "status": "complete", "progress_pct": 100,
# "clips": [ { "id": "...", "output_url": "https://...mp4",
# "duration_sec": 44.2 } ],
# "edit_receipt": { "segments": 17, "pauses_cut": 28,
# "retakes_cut": 6, "takes_combined": 4,
# "dead_air_revealed_sec": 12.3,
# "instructions_applied": ["extra-tight cuts"] } }status moves through created → transcribing → editing → rendering → complete. Poll every 10 s; a typical 60-second source finishes in two to four minutes. There are no webhooks yet.
Upload each take, then pass the extras as additional_sources with combine_takes. Order is preserved exactly as sent — the array is the running order of the finished video.
{
"mode": "post",
"storage_path": "<primary>",
"backend": "r2",
"source_url": "<primary public_url>",
"combine_takes": true,
"additional_sources": [
{ "storage_path": "...", "backend": "r2", "filename": "take-2.mp4",
"read_url": "https://..." },
{ "storage_path": "...", "backend": "r2", "filename": "take-3.mp4",
"read_url": "https://..." }
]
}Every finished job returns what was actually done to the footage — segments kept, pauses cut and for how long, retakes removed, takes combined, and any plain-English instruction that was applied. It is there so an automated pipeline can make decisions about a video it never watched.
Returns a complete script for one of the five supported short- or long-form platforms. Each call returns three hook variants (Safe / Balanced / Spicy) so you can A/B without re-prompting.
product_namestringrequired3-100 chars. The thing the creator is selling or talking about.
product_descriptionstringUp to 500 chars (4000 for youtube_long). Helps the model write specific copy.
platformenumtiktok | reels | youtube_shorts | youtube_long | facebook_reels — defaults to tiktok.
risk_tierenumSAFE | BALANCED | SPICY — controls how punchy / edgy the script gets.
persona_idstringPersona slug (e.g. skeptic, hype, parent). Free tier supports 8; paid tiers all 20+.
const res = await fetch("https://flashflowai.com/api/public/generate-script", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
product_name: "Matcha Energy Powder",
product_description: "Plant-based caffeine, no jitters",
platform: "tiktok", // tiktok | reels | youtube_shorts | youtube_long | facebook_reels
risk_tier: "BALANCED", // SAFE | BALANCED | SPICY
persona_id: "skeptic", // optional — see /admin/personas for the full set
}),
});
const { ok, skit } = await res.json();
if (!ok) throw new Error("Generation failed");
console.log(skit.hook_variants[0].spoken);{
"ok": true,
"skit": {
"hook_variants": [
{
"tier": "SAFE",
"stack": [
{ "role": "callout", "rail": "neutral", "line": "stops the scroll" },
{ "role": "escalate", "rail": "pain", "line": "turns the problem up" },
{ "role": "escalate", "rail": "desire", "line": "turns the want up" }
],
"spoken": "every layer joined — the line you actually say",
"visual": "...", "on_screen": "...",
"stack_notes": [], "stack_seconds": 6.3
},
{ "tier": "BALANCED", "stack": [ ... ], "spoken": "...", "visual": "...", "on_screen": "..." },
{ "tier": "SPICY", "stack": [ ... ], "spoken": "...", "visual": "...", "on_screen": "..." }
],
"hook_line": "...",
"beats": [
{ "t": "0:00", "action": "...", "dialogue": "...", "on_screen_text": "..." }
],
"cta_line": "...",
"cta_overlay": "...",
"b_roll": ["..."],
"overlays": ["..."]
}
}JSON body on failure: { "error": "...", "correlation_id": "..." }. Include the correlation_id in any support email.
x-ratelimit-remaining, x-ratelimit-reset.Higher rate limits, persona presets, and direct support are part of the closed-beta API tier. Drop a note and we'll get you set up.
Request API accessThe free script generator on this site uses the same endpoint. Pricing on /pricing.