Videos, shorts, and Voiceovers
Generate media, upload source files, check progress, and retrieve downloads through the API.
Use https://api.jellypod.com/v1 as the base URL. Authenticate each API
request with Authorization: Bearer YOUR_API_KEY. Create a key in
Developers.
Videos, shorts, and voiceovers have separate collections, matching Studio.
A short appears under /shorts; a voiceover appears under /voiceovers.
Discover settings before creating
Call GET /videos/options, GET /shorts/options, or
GET /voiceovers/options for each product's supported settings and default
values. These are fixed product fallbacks, not anything saved for your
organization; voiceover has no style selection.
Use these shared endpoints alongside product options:
| Endpoint | Use |
|---|---|
GET /account | Check your organization, plan, and data.credits.balance. |
GET /hosts | Find a narrator owned by your organization. |
GET /sources | Find existing research or uploaded visual sources. |
Prompt and voiceover creation require a narrator: call GET /hosts to find
one, then send its ID in host_id, or up to four in host_ids for a video or
Voiceover. Finished scripts resolve their bracketed speaker labels instead.
Create and retrieve media
| Action | Video | Short | Voiceover |
|---|---|---|---|
| Create | POST /videos | POST /shorts | POST /voiceovers |
| List | GET /videos | GET /shorts | GET /voiceovers |
| Retrieve | GET /videos/{id} | GET /shorts/{id} | GET /voiceovers/{id} |
| Progress | GET /videos/{id}/status | GET /shorts/{id}/status | GET /voiceovers/{id}/status |
| Retry | POST /videos/{id}/retry | POST /shorts/{id}/retry | POST /voiceovers/{id}/retry |
| Download | GET /videos/{id}/download | GET /shorts/{id}/download | GET /voiceovers/{id}/download |
Generation uses the same product credit rates as Studio.
Status checks and downloads do not generate new content. Open the returned
studio_url to edit narration or individual scenes.
Idempotency keys
Create and retry requests accept an optional Idempotency-Key header: 1 to 255
printable ASCII characters, without spaces. Without it, every request starts a
new generation, including an automatic retry of a request whose response was
lost. With it, resending the same request within 24 hours returns the original
resource instead of generating again. Reusing a key with different inputs
returns 409. A deliberate retry of a failed generation uses the retry
endpoint and a new key.
The header applies to the product endpoints above and to
POST /episodes/generate. Other writes do not gain idempotency protection
from it.
Create a Video
Send this JSON to POST /videos:
{
"prompt": "Explain how bees communicate, for a curious beginner.",
"host_ids": ["host_id_1", "host_id_2"],
"target_duration_seconds": 300,
"language": "en",
"orientation": "LANDSCAPE"
}Video durations are 300, 420, 600, 900, or 1200 seconds; omit
target_duration_seconds to default to 300 seconds. host_ids accepts one
to four unique host IDs in cast order. A video may send one host_id instead,
but a request cannot include both fields, and a narrator is required.
Create a short
{
"prompt": "Explain how bees communicate, for a curious beginner.",
"target_duration_seconds": 30,
"background_music": true
}Shorts support 30 or 60 seconds and default to 30. background_music
is optional and defaults to on.
Create from a finished script
Videos and shorts accept script instead of prompt:
{
"script": "[Alex]\nWelcome to the show.\n\n[Sam]\nToday we are explaining how bees communicate.",
"style_id": "your_style_id"
}Put [Character Name] on its own line before each turn. Each label matches an
active character returned by GET /hosts, or assign Host IDs to labels
explicitly with speaker_characters:
{
"speaker_characters": [{ "label": "Alex", "character_id": "host_id_1" }]
}Explicit assignments override name matching and must use active hosts; unmapped
labels still match by name. Script mode does not accept host_id, host_ids,
or source_ids. Video and short scripts support at most four speakers.
style_id is required. Jellypod validates and resolves the complete script
before starting Credit-bearing generation.
Create a Voiceover
First upload a supported document or image. Send this JSON to
POST /voiceovers, replacing the example UUID with your source ID:
{
"source_ids": ["00000000-0000-4000-8000-000000000001"],
"direction": "Walk through the slides in order for new team members.",
"captions_enabled": true,
"visual_motion": "auto"
}source_ids contains 1 to 10 unique uploaded-file source IDs in presentation
order. The combined input can contain at most 50 visuals. Generation waits for
Source processing and prepares missing visuals.
Omit target_duration_seconds for automatic duration based on the content.
Explicit durations are 180, 300, 420, 600, 900, 1200, 1800, 2700,
or 3600 seconds, subject to your plan's voiceover limit. direction accepts up
to 4,000 characters. captions_enabled defaults to false. visual_motion
defaults to auto, which zooms photos and holds document pages still.
Send dynamic to zoom every visual or static to zoom none.
Optional settings
All three products accept language and orientation. In prompt mode, shorts
accept one narrator in host_id; videos accept one to four narrators in
host_ids and retain host_id as a one-narrator compatibility input.
Voiceovers use the same narrator fields. language defaults to en when
omitted, and orientation defaults to AUTO.
Orientation accepts AUTO, LANDSCAPE, PORTRAIT, or SQUARE. Omitting
orientation, or sending AUTO explicitly, means portrait for shorts and
landscape for videos and voiceovers. Portrait and square are also supported
for voiceovers.
Videos and shorts require exactly one of a nonblank prompt or script.
Prompts accept up to 20,000 characters and scripts up to 75,000. Prompt mode
also accepts style_id from product options and up to 100 unique source_ids
for research. Style defaults to the product's default style when omitted, but
script mode requires it.
Processing sources can be attached immediately: the generation workflow waits
before reading them. Missing, failed, or stalled sources are rejected.
Complete the upload before attaching a newly reserved source.
background_music belongs to shorts. captions_enabled, direction, and
visual_motion belong to voiceovers, which do not accept prompt or
style_id.
Follow progress and download
Creation returns HTTP 202 with the resource in data, a Location header
pointing to its status endpoint, and Retry-After: 10. Save data.id
immediately. The resource also includes object, title, status, phase,
error, generation_id, studio_url, host_id, host_ids, language,
orientation, background_music, style_id, target_duration_seconds,
created_at, and updated_at.
Poll the status endpoint. status is idle, generating, completed, or
failed. Use phase for progress detail and error for a failure reason.
Respect poll_after_seconds; generation can continue after your script stops.
Once generation completes, request /download. Its data contains id,
object, and delivery:
{"kind":"available","url":"https://example.com/output.mp4","origin":"mux"}contains the download URL. The origin may also bestorage.{"kind":"preparing","reason":"rendition"}means the delivery file is still being prepared. The reason may also beingest. Wait and check again.{"kind":"unavailable"}means no downloadable output is available. Open the resource in Studio.
List endpoints accept limit (1 to 100, default 20) and an opaque cursor.
They return {"data": [...]} with the newest items first. Follow the response's
Link header with rel="next" for the next page; no next link means the end.
Pagination metadata is in the HTTP header, not in the JSON body.
Upload a file for Voiceover
Request an upload slot with POST /sources/uploads, upload the bytes to
data.upload.url with its method and headers, then call
POST /sources/{id}/complete-upload. Use the actual byte size and MIME type.
Upload slots expire at data.upload.expires_at. Send the API key only to
Jellypod API endpoints, not the signed upload URL.
Files must be nonempty and no larger than 100 MiB (104,857,600 bytes).
file_name must be a filename without directories, at most 255 characters;
its extension must match mime_type. Optional title accepts 1 to 500
characters. Completion verifies the declared size, stored MIME type, and
supported file signatures.
Voiceover supports these source types:
| File | MIME type |
|---|---|
application/pdf | |
PowerPoint .pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation |
| PNG | image/png |
JPEG .jpg or .jpeg | image/jpeg |
| WebP | image/webp |
Other presentation formats (.ppt, .pptm, Keynote, OpenDocument) are not
accepted for Voiceover; export to PDF or .pptx first.
A voiceover supports 50 pages or images combined across all its sources. Splitting a presentation into multiple files does not raise this total.
Other supported upload types can be used as research sources. API uploads do
not accept video/*; use extracted audio instead. Use POST /sources for a
URL, YouTube link, or inline text.
A source moves from awaiting_upload through processing to completed or
error. Check GET /sources/{id}. Request a new slot if it expires or
processing fails.
Handle errors and retries
Read the error before retrying. Correct invalid input, add credits when needed,
and respect Retry-After for rate limits. A failed HTTP response does not prove
that generation failed.
Errors can include a recovery object: an action such as correct_input,
check_account, or retry, and, on a pre-execution rejection, an
idempotency_key of reuse or new. This field is present even when the
request sent no Idempotency-Key; follow it only when you sent one. A
rejection proven to happen before generation started releases its key, so fix
the input or account issue and resend with the same key when
idempotency_key is reuse; new means start over with a different key.
If an idempotency conflict says acceptance is unresolved, check the resource ID in the error. Do not create again with a new key because the original request may be running. Completed requests replay their accepted response.
A resource with status: "failed" or an unstarted status: "idle" can be
retried with POST /{collection}/{id}/retry. Send a new Idempotency-Key if
the retry itself might be resent. The retry keeps the resource ID and starts a
new generation attempt. Review the error and credits first.
Was this page helpful?