Generate a video end to end
The full pipeline, one step at a time: content → voiceover → render.
If you want it collapsed into a single request, see One-shot generation. The reason to do it stepwise is that you get to read and correct the script before spending credits on a render, which the one-shot path does not offer.
Scopes: read + generate.
1. Create the content item
curl -s -X POST https://api.botlobby.ai/v1/contents \
-H "Authorization: Bearer $BOTLOBBY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"full_text": "The James Webb telescope found water vapour on K2-18b",
"persona_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"title": "Water on a distant world",
"related_description": true
}'
full_text is the topic or source material — the script is generated from
it, so it does not need to be the script itself. persona_id comes from
GET /personas and decides the voice and models.
related_description controls whether the generated description relates to the
video or is generic filler.
The id comes back nested under item — read item.id, not id. It is the
creation_id and content_id everywhere below.
2. Read the script, and fix it if needed
This is the step the one-shot path skips.
curl -s https://api.botlobby.ai/v1/contents/CONTENT_ID \
-H "Authorization: Bearer $BOTLOBBY_API_KEY"
textover is the script. To change it you need the write scope:
curl -s -X PUT https://api.botlobby.ai/v1/contents/CONTENT_ID \
-H "Authorization: Bearer $BOTLOBBY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"textover": "In 2026, astronomers confirmed..."}'
A successful edit sets status back to NEW, discarding any audio or video
already generated. Edit before generating, not after.
If a job is already running you get 409 with
"code": "generation_in_progress". Resend with "cancel_generation": true to
cancel it and apply the edit.
3. Generate the voiceover
curl -s -X POST https://api.botlobby.ai/v1/gen/tts \
-H "Authorization: Bearer $BOTLOBBY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"creation_id": "CONTENT_ID",
"audio_prompt": "Calm and documentary, slightly slower than normal"
}'
audio_prompt is a direction for the narrator, not the script — the script
comes from the content item. It is capped at 2000 bytes of UTF-8 — bytes,
not characters, so non-ASCII text reaches the limit sooner than its length
suggests. Over that returns 400 rather than reserving credits and failing
inside the worker.
Then wait for AUDIO_PROCESSED:
curl -s https://api.botlobby.ai/v1/gen/task-status/CONTENT_ID \
-H "Authorization: Bearer $BOTLOBBY_API_KEY"
See Polling and job status for a proper loop.
4. Choose a visual source
video_id decides what the viewer sees. It takes either a specific background
or one of four mode sentinels.
A specific background
curl -s https://api.botlobby.ai/v1/gen/static-videos \
-H "Authorization: Bearer $BOTLOBBY_API_KEY"
[
{
"name": "1",
"cdn_url": "https://cdn.botlobby.ai/static-videos/1.mp4",
"thumbnail_cdn_url": "https://cdn.botlobby.ai/static-videos/1.jpg"
}
]
id fieldname is the video_id. "1" resolves to static-videos/1.mp4. Looking
for an id and finding none is the usual first stumble here.
Or a sentinel
video_id | What you get |
|---|---|
stock | Stock footage and photography matched to the script |
web_search | Images found on the web for the topic |
generate | AI illustrations drawn per beat of the script |
custom | Your own uploaded background |
A sentinel selects a mode, not a file. Do not send video_file_name
alongside one.
Visual modes vs. video_id
Your plan controls which visual modes you may use, and
GET /subscription/current reports them under
limits.content_limits.allowed_visual_modes. That list uses five names —
the four sentinels above, plus static.
static is the odd one out: it is not a value you send. It is the mode that
any non-sentinel video_id resolves to. Passing the library id "1" is
using static mode.
Mode in allowed_visual_modes | What to send as video_id |
|---|---|
static | A name from GET /gen/static-videos, e.g. "1" |
custom | "custom" |
stock | "stock" |
web_search | "web_search" |
generate | "generate" |
video_id: "static"It isn't a sentinel, so it's treated as a library id — which means it passes
the plan check and then fails at render, because no clip is named static.
Send an actual id from GET /gen/static-videos.
Static and custom backgrounds are free to source; stock and web search carry a per-build credit surcharge that scales with video length.
5. Render
curl -s -X POST https://api.botlobby.ai/v1/gen/video \
-H "Authorization: Bearer $BOTLOBBY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"creation_id": "CONTENT_ID",
"video_id": "stock",
"orientation": "vertical",
"prefer_real_world": true,
"zoom_effect_enabled": true
}'
Options worth knowing:
| Field | Notes |
|---|---|
orientation | vertical (9:16, default) or landscape (16:9) |
seconds_per_image | How long each image holds. Only changes cost under generate |
prefer_real_world | Under stock, searches Wikimedia Commons before Pexels — better for named people and current events |
illustration_style | Only meaningful with video_id: generate |
custom_style_description | Only when illustration_style is custom. Truncated at 200 characters — a style hint, not a second prompt |
zoom_effect_enabled | Ken Burns zoom. Image-based sources only; static and custom backgrounds are real video |
image_overlay_enabled | Small image over part of the frame. Static, custom and AI-generated backgrounds only |
This endpoint is subject to your plan's daily video cap as well as the
ordinary rate limit. Both appear as 429 with Retry-After.
Poll until VIDEO_PROCESSED.
6. Collect the result
curl -s https://api.botlobby.ai/v1/contents/CONTENT_ID \
-H "Authorization: Bearer $BOTLOBBY_API_KEY"
video_url and thumbnail_url are signed and time-limited. Fetch them
fresh when you need them rather than storing the URLs; store the content_id
instead.
Next
- Publish it — needs the
uploadscope - One-shot generation — the same pipeline in one call