Jellypod Docs

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. Every operation checks that the resource belongs to your organization.

Discover settings before creating

Call GET /videos/options, GET /shorts/options, or GET /voiceovers/options for the product you want to create. Each response has a data object with supported_duration_seconds, styles (each with id, name, and description), and your organization's default_host_id, default_language, default_orientation, default_style_id, and default_duration_seconds. Voiceover has no style selection, so its styles array is empty and its default style and duration are null.

Use these shared endpoints alongside product options:

EndpointUse
GET /accountCheck your organization, plan, and data.credits.balance.
GET /hostsFind a narrator owned by your organization.
GET /voicesBrowse voices when creating or updating a Host.
GET /sourcesFind existing research or uploaded visual Sources.

If default_host_id is null, choose a Host and include its ID in host_id, or save a default narrator in Studio. Product options describe available settings; creation still checks your plan limits and credits.

Create and retrieve media

ActionVideoShortVoiceover
CreatePOST /videosPOST /shortsPOST /voiceovers
ListGET /videosGET /shortsGET /voiceovers
RetrieveGET /videos/{id}GET /shorts/{id}GET /voiceovers/{id}
ProgressGET /videos/{id}/statusGET /shorts/{id}/statusGET /voiceovers/{id}/status
RetryPOST /videos/{id}/retryPOST /shorts/{id}/retryPOST /voiceovers/{id}/retry
DownloadGET /videos/{id}/downloadGET /shorts/{id}/downloadGET /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.

Save an idempotency key

Create and retry requests for these three products require an Idempotency-Key header: 1 to 255 printable ASCII characters, without spaces. Save a unique key before sending the request. If the response is lost, resend the same request with that key. Reusing it with different inputs returns 409. A deliberate retry of a failed generation uses the retry endpoint and a new key.

This requirement applies to the product endpoints above. For Podcasts, POST /episodes/generate accepts an optional key; other writes do not gain idempotency protection from this header.

Create a Video

Send this JSON to POST /videos with your API key, Content-Type: application/json, and a saved Idempotency-Key:

{
  "prompt": "Explain how bees communicate, for a curious beginner.",
  "target_duration_seconds": 300,
  "language": "en",
  "orientation": "LANDSCAPE"
}

Video durations are 300, 420, 600, 900, or 1200 seconds. Omit target_duration_seconds to use a supported saved duration, or 420 seconds if no supported duration is saved.

Create a Short

curl --fail-with-body --max-time 30 \
  'https://api.jellypod.com/v1/shorts' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: YOUR_SAVED_UNIQUE_REQUEST_KEY' \
  --data '{
    "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 uses your saved setting when omitted.

Create a Voiceover

First upload and process a supported document or image with prepare_visuals: true, as shown below. Send this JSON to POST /voiceovers with your API key, JSON content type, and a saved Idempotency-Key. Replace the example UUID with your completed 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": "static"
}

source_ids contains 1 to 10 unique uploaded-file Source IDs in presentation order. The combined input can contain at most 50 visuals. Voiceover requires completed Sources with prepared visuals; a completed text extraction alone is not enough.

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 accepts static (the default) or dynamic.

Optional settings

All three products accept host_id, language, and orientation. The narrator and language use your saved organization settings when omitted. Orientation accepts AUTO, LANDSCAPE, PORTRAIT, or SQUARE; omitted or AUTO means portrait for Shorts and landscape for Videos and Voiceovers. An explicit portrait or square orientation is also supported for Voiceovers.

Videos and Shorts require a nonblank prompt of up to 20,000 characters. They also accept style_id from product options and up to 100 unique source_ids for research. Style defaults to your saved setting. 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.

Request bodies reject unknown fields. background_music belongs to Shorts; captions_enabled, direction, and visual_motion belong to Voiceovers. Voiceover uses uploaded visuals and does 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, style_id, target_duration_seconds, created_at, and updated_at.

Poll the status endpoint, which returns this shape:

{
  "data": {
    "id": "00000000-0000-4000-8000-000000000001",
    "object": "short",
    "status": "generating",
    "phase": null,
    "error": null,
    "generation_id": null,
    "studio_url": "https://studio.jellypod.com/shorts/00000000-0000-4000-8000-000000000001",
    "poll_after_seconds": 10
  }
}

status is idle, generating, completed, or failed. Use phase for progress detail when present and error for a failure reason. Respect poll_after_seconds and stop polling at a reasonable time limit; generation can continue after your script stops. completed and failed are terminal states for the current attempt.

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 be storage.
  • {"kind":"preparing","reason":"rendition"} means the delivery file is still being prepared. The reason may also be ingest. 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.

Developers includes JavaScript, Python, and cURL examples with bounded status and download polling for each product. Python examples require requests; shell examples require Bash, cURL, and jq. The JavaScript examples run in Node.js with built-in fetch.

Upload a file for Voiceover

Request an upload slot with POST /sources/uploads, upload the file bytes to data.upload.url with the returned method and headers, then call POST /sources/{id}/complete-upload. Set prepare_visuals: true when requesting PDF, presentation, or image sources for Voiceover. Use the file's actual byte size and matching MIME type. Upload slots expire after two hours, at data.upload.expires_at; finish both the byte upload and completion request before that time.

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.

With prepare_visuals: true, supported types are:

FileMIME type
PDFapplication/pdf
PowerPoint .pptapplication/vnd.ms-powerpoint
PowerPoint .pptxapplication/vnd.openxmlformats-officedocument.presentationml.presentation
PowerPoint .pptmapplication/vnd.ms-powerpoint.presentation.macroEnabled.12
Keynote .keyapplication/vnd.apple.keynote
OpenDocument .odpapplication/vnd.oasis.opendocument.presentation
PNGimage/png
JPEG .jpg or .jpegimage/jpeg
WebPimage/webp

Each visual Source is limited to 50 pages or images. Larger presentations must be split into separate generation requests to respect the combined 50-visual Voiceover limit.

prepare_visuals defaults to false. General research uploads also accept supported text, document, spreadsheet, image, and audio types, such as text/plain (.txt), text/markdown (.md), text/csv (.csv), application/vnd.openxmlformats-officedocument.wordprocessingml.document (.docx), and audio/mpeg (.mp3). API uploads do not accept video/* MIME types; upload extracted audio for video research. Use POST /sources for a URL, YouTube link, or inline text instead of a file slot.

A reserved Source starts at awaiting_upload. After completion it moves through processing to completed or error. Check GET /sources/{id}. Calling complete-upload again for an already processing or completed Source returns its current record. If the slot expires or processing fails, request a new slot. A 409 during completion means another completion is in progress; retrieve the Source before retrying.

If your client cannot transfer local files, open Studio's Assets library and choose New Asset. Supported documents and images uploaded there are prepared for Voiceover. Use GET /sources to find the Source ID, then wait for completion before creating a Voiceover.

This Node.js example uploads a PDF and waits for the Source to finish processing. It prints the Source ID to use in the Voiceover quickstart.

import { readFile } from 'node:fs/promises';

const base = 'https://api.jellypod.com/v1';
const authorization = 'Bearer YOUR_API_KEY';
async function api(path, options = {}) {
  const response = await fetch(base + path, {
    ...options,
    headers: { Authorization: authorization, ...options.headers },
    signal: AbortSignal.timeout(30_000),
  });
  const result = await response.json();
  if (!response.ok) throw new Error(JSON.stringify(result));
  return result.data;
}

const bytes = await readFile('./presentation.pdf');
const slot = await api('/sources/uploads', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    file_name: 'presentation.pdf',
    mime_type: 'application/pdf',
    size_bytes: bytes.byteLength,
    prepare_visuals: true,
  }),
});
console.log('Save this Source ID:', slot.source.id);
const upload = await fetch(slot.upload.url, {
  method: slot.upload.method,
  headers: slot.upload.headers,
  body: bytes,
  signal: AbortSignal.timeout(120_000),
});
if (!upload.ok) throw new Error('File upload failed: ' + upload.status);
await api('/sources/' + slot.source.id + '/complete-upload', { method: 'POST' });
for (let attempt = 0; attempt < 120; attempt++) {
  const source = await api('/sources/' + slot.source.id);
  if (source.status === 'completed') {
    console.log('YOUR_PROCESSED_SOURCE_ID:', source.id);
    break;
  }
  if (source.status === 'error') throw new Error('Source processing failed. Request a new upload slot.');
  if (attempt === 119) throw new Error('Polling stopped. Check the saved Source ID before uploading again.');
  await new Promise(resolve => setTimeout(resolve, 10_000));
}

Send the Jellypod API key only to Jellypod API endpoints. The file transfer uses the signed upload URL and headers returned by the API.

Handle errors and retries

Read the error response before retrying. Invalid input needs correction; insufficient credits require an available balance. A recorded rejection before dispatch replays the same error; after correcting it, use a new key for the new request. For rate limits, respect Retry-After. A failed HTTP response does not prove that generation failed.

If an idempotency conflict says acceptance is unresolved, check the resource ID in the error. Do not create again with a new key: the original dispatch may already be running. Keep the command ID for support if the resource cannot be found. Completed requests replay their accepted response; fetch /status for current progress.

A resource with status: "failed" or an unstarted status: "idle" can be retried with POST /{collection}/{id}/retry and a new Idempotency-Key. The retry keeps the resource ID and starts a new generation attempt. Review the error and credits before starting that attempt.

Was this page helpful?

Ready to create your podcast?

Go from idea to published episode in minutes. No recording, editing, or experience required.

Pricing on your terms

Pick the plan that works best for you

Pricing details

Start Podcasting

Publish your first episode in minutes

Open the Studio