On this page
- Short answer
- Build it in six steps
- What a text-to-video app costs in generation (17 October 2026)
- What does a text-to-video app look like?
- How does the server create the video job?
- How does the app know a video is ready?
- Where should the finished MP4 go?
- How does the page show progress?
- What does a text-to-video app cost to run?
- How do I stop one user from draining the balance?
- What should I check before launch?
- Frequently asked questions
Short answer
A text-to-video app needs four parts: a form, a server route that calls POST /v1/videos, a webhook that saves the finished MP4 to your storage, and a status endpoint the page can poll. On KreatorFlow a 5-second 720p draft costs $0.45 and a final Seedance 2.0 clip $0.55, as of 17 October 2026.
Get started
Build it in six steps
- 1
Get a key and a webhook secret
Create an API key (videos:create, jobs:read) and copy the workspace webhook signing secret from the developer console. Both live in server environment variables only.
- 2
Add a create route
Your server receives the prompt, validates it, calls POST /v1/videos with a webhook_url and an Idempotency-Key, and stores the job id against the user.
- 3
Handle the webhook
A route verifies the signature, dedupes on the event id and queues the work, then answers 200 straight away.
- 4
Save the output
A worker downloads /v1/jobs/{id}/output with your key and writes the MP4 to your storage, then marks the record completed.
- 5
Show progress
The page polls your own status endpoint every few seconds and swaps the spinner for the video when its URL appears.
- 6
Control spend
Limit clip length and resolution per user, draft on the Fast model, and check balance_tokens in each create response so you can top up before it runs out.
What a text-to-video app costs in generation (17 October 2026)
One 5-second clip at 720p without a video input, from the KreatorFlow developer API job quote, and the same clip multiplied out. Storage, delivery and hosting are not included.
| Clip | Per clip | 100 clips | 1,000 clips | 10,000 clips |
|---|---|---|---|---|
| Seedance 2.0 Fast, 5 s at 720p | $0.45 | $45.00 | $450.00 | $4,500.00 |
| Seedance 2.0, 5 s at 720p | $0.55 | $55.00 | $550.00 | $5,500.00 |
| Seedance 2.5, 5 s at 720p | $1.05 | $105.00 | $1,050.00 | $10,500.00 |
What does a text-to-video app look like?
Four moving parts, all on your side except the generation itself. The browser only ever talks to your server; the API key, the webhook secret and the storage credentials never leave it.
- Frontend: a prompt box, settings you choose to expose (length, aspect ratio), and a result view
- Create route: validates input, calls POST /v1/videos, saves the job id
- Webhook route + worker: verifies the event, downloads the MP4, stores it
- Storage + CDN: where finished videos live and are served from
How does the server create the video job?
The route below takes a prompt, asks for a 5-second vertical clip on Seedance 2.0 Fast and points the webhook at your app. It saves the quoted token cost with the record, which is handy for per-user limits and invoices. Swap the model for Seedance 2.0 (kreator-video-seedance-2) when users want final quality or references.
Create route (Next.js, TypeScript)
// app/api/videos/route.ts: your server creates the job, never the browser import { db } from "@/lib/db"; export async function POST(request: Request) { const { prompt, userId } = await request.json(); if (!prompt || prompt.length > 2000) return Response.json({ error: "Write a prompt" }, { status: 400 }); const res = await fetch("https://kreatorflow.ai/v1/videos", { method: "POST", headers: { Authorization: `Bearer ${process.env.KREATORFLOW_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify({ model: "kreator-video-seedance-2-fast", prompt, duration: 5, aspect_ratio: "9:16", resolution: "720p", webhook_url: `${process.env.APP_URL}/api/kreatorflow/webhook`, }), }); const body = await res.json(); if (!body.success) return Response.json({ error: body.error, code: body.code }, { status: res.status }); const job = body.data.job; await db.video.create({ data: { userId, prompt, jobId: job.id, status: job.status, quotedTokens: body.data.quoted_token_cost } }); return Response.json({ jobId: job.id, status: job.status }); }
How does the app know a video is ready?
Through the webhook. KreatorFlow posts a signed job.completed (or job.failed) event to your webhook_url; verify the HMAC-SHA256 signature, ignore repeated event ids and hand the job to a worker. The webhooks guide has complete handlers for Next.js, Express and Flask.
Where should the finished MP4 go?
Into storage you control, served through a CDN. The /v1/jobs/{id}/output link is for fetching the file with your key, not for showing to users, so the worker downloads it once and writes it to your bucket.
The link is private (it needs your API key) and expires, as providerOutputExpiresAt shows, so copy the file on the job.completed webhook. Any S3-compatible store (S3, R2, B2 and similar) works with the code below; the storage guide compares the options and shows the pattern in more detail.
Worker: download and store (TypeScript)
// worker.ts: runs for each verified job.completed webhook (see the webhooks guide) import { db } from "@/lib/db"; import { putObject } from "@/lib/storage"; // your S3-compatible client (S3, R2, B2, ...) export async function handleCompleted(job: { id: string; providerOutputUrl: string }) { const file = await fetch(job.providerOutputUrl, { headers: { Authorization: `Bearer ${process.env.KREATORFLOW_API_KEY}` }, }); if (!file.ok) throw new Error(`Download failed: ${file.status}`); const key = `videos/${job.id}.mp4`; await putObject(key, Buffer.from(await file.arrayBuffer()), "video/mp4"); await db.video.update({ where: { jobId: job.id }, data: { status: "completed", fileKey: key } }); }
How does the page show progress?
Let the page poll your database, not the KreatorFlow API: it is faster, it never exposes your key, and it costs you nothing per request. A simple status route returns processing until the worker has stored the file, then the CDN URL.
Status route (Next.js, TypeScript)
// app/api/videos/[jobId]/route.ts: the browser polls YOUR database, not the API import { db } from "@/lib/db"; export async function GET(_: Request, { params }: { params: Promise<{ jobId: string }> }) { const { jobId } = await params; const video = await db.video.findUnique({ where: { jobId } }); if (!video) return Response.json({ error: "Not found" }, { status: 404 }); return Response.json({ status: video.status, url: video.fileKey ? `${process.env.CDN_URL}/${video.fileKey}` : null }); }
What does a text-to-video app cost to run?
Generation is the main cost and it scales linearly: every clip is billed per second of video at its model and resolution. The table shows a 5-second 720p clip and what 100, 1,000 and 10,000 of them cost, from the API's own job quote as of 17 October 2026. Storage and delivery come on top, and your hosting and database are usually small next to generation.
Three settings move the bill most: length (cost is per second), resolution (1080p and 4K cost more per second than 720p) and model (Fast for drafts, the full model for finals). Letting users preview on Fast and render only the keeper on the full model keeps most of the spend on the cheaper rate.
How do I stop one user from draining the balance?
Put the limits in your create route: cap clips per user per day, cap duration and resolution by plan, and reject prompts over your own length limit before calling the API. Every create response includes balance_tokens; alert yourself when it falls under a threshold and top up before it reaches zero (a create without enough tokens returns 402 insufficient_balance).
Pass safety_identifier (for example a hash of your user id) on each request so a job can be tied to one of your users without sending personal data.
What should I check before launch?
A short checklist that catches most production issues:
- The API key and webhook secret are only in server environment variables
- Every create call sends a fresh Idempotency-Key
- The webhook route verifies signatures and dedupes event ids
- A slow sweep polls jobs still processing after a while
- Finished files are copied to your storage and served from your CDN
- Per-user limits and a low-balance alert are in place
Frequently asked questions
Can I build a text-to-video app without a backend?
No. The API key must stay secret, so the call has to come from a server or a serverless function. A small Next.js app on any host is enough.
Which model should my app use?
Seedance 2.0 Fast for previews and high volume, Seedance 2.0 for final clips and references, and Seedance 2.5 when users need long single takes. GET /v1/models lists what your key can use.
How long do users wait?
Usually a few minutes per clip, longer for long or high-resolution ones. Show a progress state, notify users when the clip is ready, and let them leave the page.
Do I need my own storage?
Yes. The output link from the API is a private, expiring download, so keep finished files in your own S3-compatible bucket and serve them from your CDN.
Can users upload their own images?
Yes. Upload the file through POST /v1/uploads (then PUT the bytes and POST /v1/uploads/complete) and pass the upload id as a reference or first frame. Only use images your users have the rights to.
How do I price my app?
Start from the per-clip cost in the table, add storage and delivery, then a margin. Charging per clip or per second keeps your price tied to your cost.



