Generating a short AI video from a playground UI is easy.
Putting video generation inside a real product is a completely different engineering problem.
That becomes pretty obvious with models like Seedance 2.5. Once you’re dealing with longer generation windows, multiple reference assets, and different output resolutions, the workflow starts looking less like "send a prompt, get a response" and more like a small media pipeline.
At that point, I don’t think a video generation should be treated as a normal synchronous API request.
It’s a background job.
The basic flow looks more like this:
text
Create video task
-> Receive task ID
-> Persist job state
-> Poll for completion
-> Download output
-> Store the final asset
That one change affects almost everything around the model.
Store the job before calling the model
I wouldn’t let the frontend directly call a video generation endpoint.
I’d create the job in my own database first:
{
"id": "video_1842",
"status": "queued",
"model": "seedance-2-5",
"prompt": "A slow tracking shot through a quiet night market",
"provider_task_id": null
}
A worker can then pick up the job and send the actual request.
Once the provider returns a task ID, save it immediately.
From there, the internal state is simple:
queued
-> processing
-> completed
-> failed
This feels like extra infrastructure when you’re building a weekend demo.
It stops feeling unnecessary as soon as several users are generating videos at once, someone refreshes the page halfway through a render, or they come back an hour later expecting the result to still be there.
The job should exist independently of the browser session.
Retries get expensive with video
This is one of the biggest differences from ordinary LLM calls.
With text models, retrying a failed request can be relatively cheap.
With video, a blind retry might mean rendering the entire clip again.
So I’d distinguish between a few different failures:
- The API rejects the request before creating a task.
- The network times out after the task may already have been accepted.
- The model fails during generation.
- The video finishes, but downloading or storing the result fails.
Those shouldn’t all trigger the same retry behavior.
If I already have a provider task ID, I’d much rather check that task again than submit another generation and risk paying twice.
That also means the provider task ID is something I’d persist as early as possible.
References are part of the data model now
The prompt also stops being the whole request once the model accepts reference assets.
A generation might depend on:
- a product image
- a character reference
- a previous clip
- a style reference
- an audio track
- the generated outputs from earlier attempts
At that point, I’d probably model assets separately and attach them to generation jobs.
That gives you a much cleaner way to reuse the same character, product, or style reference across multiple renders without repeatedly uploading and rebuilding everything.
It also makes the system easier to debug.
If a render looks wrong, you can see exactly which assets were attached to that attempt.
Cost per accepted clip matters more than cost per second
Video pricing can also be misleading if you only look at the advertised generation rate.
If a team generates a clip three times before accepting one of them, the real cost of that asset includes all three attempts.
So I’d track more than raw generation price.
A few metrics I’d want are:
- total generation spend
- accepted clips
- attempts per accepted clip
- generation time
- failure rate
- duration
- resolution
- model used
The metric I’d probably care about most is:
total generation spend / accepted clips
A cheaper model can easily become more expensive in practice if people have to regenerate its output again and again.
Keep the provider behind the worker
The other thing I’d avoid is spreading provider-specific logic throughout the application.
Ideally, the product layer only needs to create something like this:
{
"prompt": "A slow tracking shot through a quiet night market",
"duration": 10,
"resolution": "720p",
"model": "seedance-2-5"
}
The worker handles the provider-specific details.
That boundary becomes useful very quickly when you want to test another model.
I’ve been looking at this through CometAPI because Seedance can sit alongside other image and video models behind the same API layer.
For me, the useful part isn’t saving one HTTP integration.
It’s keeping model routing outside the core product.
A cheap model could handle draft generations, while a stronger model handles the more expensive reference-heavy jobs.
The rest of the application doesn’t need to care.
A production pipeline might end up looking something like this:
Client
-> Ingestion API
-> Database
-> Job Queue
-> Worker
-> Video Model
-> Object Storage
-> Completed Job
Seedance 2.5 happens to be the model in this example.
The architecture shouldn’t depend on it staying the best model.
That’s what makes the pipeline useful after the next model release too.
Disclosure: This post is adapted from research originally published by the CometAPI team.

Top comments (1)
Great article! Having quick access to free APIs for testing and prototyping really speeds up development. API aggregator platforms are worth checking out for discovering services by category. Keep it up! 🚀