Source-to-media API recipes
Upload a source, request a Voiceover or illustrated Video, check progress, and retrieve the generated file.
These examples follow the current public API contracts. They have been checked against the request schemas and existing automated tests; an isolated upload-to-playback run has not yet been recorded. They are reference examples, not a claim of verified output quality or editor parity.
Use a PDF or PowerPoint source for a Voiceover that narrates its prepared pages. Use the same source as research for an illustrated Video that generates new visuals. Review the narration and output before sharing. For PowerPoint, use PDF if you need control over how each slide renders; do not assume editable objects, animations, or transitions survive page preparation.
Before you start
You need curl, jq, a supported local file, and an organization API key from
Developers. API key access
requires Creator or higher. Set JELLYPOD_API_KEY in your shell environment;
keep it out of request files, source control, and logs. Run these examples in a
private working directory with a synthetic source before using customer data.
set -euo pipefail
umask 077
: "${JELLYPOD_API_KEY:?Set your organization API key in the environment}"
API_BASE='https://api.jellypod.com/v1'
curl --fail-with-body --silent --show-error --proto '=https' \
-H "Authorization: Bearer $JELLYPOD_API_KEY" \
"$API_BASE/account" > account.json
curl --fail-with-body --silent --show-error --proto '=https' \
-H "Authorization: Bearer $JELLYPOD_API_KEY" \
"$API_BASE/hosts?limit=100" > hosts.json
curl --fail-with-body --silent --show-error --proto '=https' \
-H "Authorization: Bearer $JELLYPOD_API_KEY" \
"$API_BASE/voiceovers/options" > voiceover-options.json
curl --fail-with-body --silent --show-error --proto '=https' \
-H "Authorization: Bearer $JELLYPOD_API_KEY" \
"$API_BASE/videos/options" > video-options.jsonCheck account.json for the organization, plan and data.credits.balance.
Select a narrator from hosts.json and set HOST_ID to its ID. Follow the
response's Link header with rel="next" if you need more hosts. The API key's
organization owns the hosts and sources; an ID from another organization will
not work. The bearer key is organization access, not a learner login. These
endpoints do not require a separate product-specific OAuth scope.
Upload one source
This example uses training.pdf. The MIME type must match the actual file.
Only upload material you have permission to process. The upload accepts a
local file, so this recipe does not ask Jellypod to fetch an arbitrary URL.
SOURCE_FILE='training.pdf'
test -f "$SOURCE_FILE"
SOURCE_BYTES=$(wc -c < "$SOURCE_FILE" | tr -d '[:space:]')
jq -n --arg name "$SOURCE_FILE" --argjson size "$SOURCE_BYTES" \
'{file_name: $name, mime_type: "application/pdf", size_bytes: $size}' \
> upload-request.json
curl --fail-with-body --silent --show-error --proto '=https' \
-H "Authorization: Bearer $JELLYPOD_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @upload-request.json \
"$API_BASE/sources/uploads" > upload-slot.json
SOURCE_ID=$(jq -er '.data.source.id' upload-slot.json)
UPLOAD_URL=$(jq -er '.data.upload.url' upload-slot.json)
UPLOAD_METHOD=$(jq -er '.data.upload.method' upload-slot.json)
UPLOAD_CONTENT_TYPE=$(jq -er '.data.upload.headers["content-type"]' upload-slot.json)
curl --fail-with-body --silent --show-error --proto '=https' \
--request "$UPLOAD_METHOD" \
-H "Content-Type: $UPLOAD_CONTENT_TYPE" \
--data-binary "@$SOURCE_FILE" "$UPLOAD_URL"
curl --fail-with-body --silent --show-error --proto '=https' \
--request POST -H "Authorization: Bearer $JELLYPOD_API_KEY" \
"$API_BASE/sources/$SOURCE_ID/complete-upload" > source-completion.jsonThe signed upload URL and headers come from upload-slot.json. Send the API
key only to Jellypod's API, never to the signed upload or download URL. Keep the
slot response private and use it before data.upload.expires_at. Do not add
--location to authenticated API requests or upload requests.
Uploads are nonempty and at most 100 MiB (104,857,600 bytes). The filename must
not contain directory separators and must match the MIME type. Voiceover
visual sources support PDF, .pptx, PNG, JPEG and WebP. For PowerPoint, use
application/vnd.openxmlformats-officedocument.presentationml.presentation.
Use 1 to 10 unique uploaded sources in presentation order, with at most 50
pages/images combined. Legacy .ppt, .pptm, Keynote and OpenDocument are not
accepted as Voiceover visuals. File completion verifies the stored size, MIME
type and supported signature.
Poll the source before creating media:
curl --fail-with-body --silent --show-error --proto '=https' \
-H "Authorization: Bearer $JELLYPOD_API_KEY" \
"$API_BASE/sources/$SOURCE_ID" > source-status.json
jq -r '.data.status' source-status.jsonContinue when data.status is completed. awaiting_upload means the upload
still needs completion; processing means wait and check again. error is a
source failure: inspect the response and correct the source. Upload reservation
and completion do not gain idempotency from a generation key. Save SOURCE_ID
and reuse the existing source rather than blindly reserving another upload.
Recipe 1: Voiceover from the prepared pages
Set HOST_ID to an organization-owned narrator from GET /hosts. Choose a
new logical request key once, then preserve that key and the exact JSON if the
HTTP response is lost. Replace the example key for each intentional new job.
HOST_ID='YOUR_ORGANIZATION_HOST_ID'
VOICEOVER_KEY='training-voiceover-request-001'
jq -n --arg source "$SOURCE_ID" --arg host "$HOST_ID" \
'{source_ids: [$source], host_id: $host,
direction: "Narrate the approved training pages in order. Preserve the stated steps and caveats.",
language: "en", orientation: "LANDSCAPE",
captions_enabled: true, visual_motion: "static"}' \
> voiceover-request.json
curl --fail-with-body --silent --show-error --proto '=https' \
-D voiceover-response-headers.txt \
-H "Authorization: Bearer $JELLYPOD_API_KEY" \
-H "Idempotency-Key: $VOICEOVER_KEY" \
-H 'Content-Type: application/json' \
--data-binary @voiceover-request.json \
"$API_BASE/voiceovers" > voiceover-response.json
VOICEOVER_ID=$(jq -er '.data.id' voiceover-response.json)visual_motion: "static" holds the prepared visuals still. auto holds
document pages still and zooms image sources; dynamic zooms every visual.
Voiceover does not accept prompt or style_id. This request omits duration
so generation chooses it from the source content, subject to the plan limit.
Explicit supported durations come from /voiceovers/options. Narration is
generated from the source and direction; review it for accuracy.
Recipe 2: Illustrated Video from the source
Reuse the completed SOURCE_ID and your narrator. Here the source is research
for a generated story and visuals, rather than a request to retain the slides.
The example uses the default Video style; choose a supported style_id from
GET /videos/options if you want to select one explicitly.
VIDEO_KEY='training-illustrated-video-request-001'
jq -n --arg source "$SOURCE_ID" --arg host "$HOST_ID" \
'{source_ids: [$source], host_id: $host,
prompt: "Explain this approved training source to new team members with illustrated scenes. Include its required steps and caveats; do not invent policy details.",
language: "en", orientation: "LANDSCAPE",
target_duration_seconds: 300}' > video-request.json
curl --fail-with-body --silent --show-error --proto '=https' \
-D video-response-headers.txt \
-H "Authorization: Bearer $JELLYPOD_API_KEY" \
-H "Idempotency-Key: $VIDEO_KEY" \
-H 'Content-Type: application/json' \
--data-binary @video-request.json \
"$API_BASE/videos" > video-response.json
VIDEO_ID=$(jq -er '.data.id' video-response.json)Video prompt mode accepts up to 100 unique research source IDs and a prompt of
at most 20,000 characters. Both recipes can use one host_id or 1 to 4 unique
host_ids, but not both. Finished-script Video creation is a separate mode:
it requires style_id, resolves bracketed speaker labels, and rejects
host_id, host_ids and source_ids. These recipes do not export an edited
timeline, custom scene timing, or the Studio's complete editor state.
Poll and download either result
Creation returns 202, data.id, a Location status path, and
Retry-After: 10. Keep the ID even if you stop polling. Use the following for
Voiceover; for Video, set COLLECTION='videos' and PRODUCT_ID="$VIDEO_ID".
COLLECTION='voiceovers'
PRODUCT_ID="$VOICEOVER_ID"
curl --fail-with-body --silent --show-error --proto '=https' \
-H "Authorization: Bearer $JELLYPOD_API_KEY" \
"$API_BASE/$COLLECTION/$PRODUCT_ID/status" > media-status.json
jq '{status: .data.status, phase: .data.phase,
error: .data.error, poll_after_seconds: .data.poll_after_seconds}' \
media-status.jsonFor generating, wait at least poll_after_seconds and poll again. Honor any
HTTP Retry-After too. Set a local observation deadline rather than poll
forever: stopping your script does not cancel generation. failed is terminal
for that attempt; inspect error before deciding whether to retry. idle
can indicate an unstarted resource. For completed, request its delivery:
curl --fail-with-body --silent --show-error --proto '=https' \
-H "Authorization: Bearer $JELLYPOD_API_KEY" \
"$API_BASE/$COLLECTION/$PRODUCT_ID/download" > delivery.json
jq '.data.delivery' delivery.jsonpreparing means the rendition or ingest is still pending: wait and check
download again. unavailable means there is no downloadable output; inspect
the resource's studio_url. Download only when the kind is available:
test "$(jq -r '.data.delivery.kind' delivery.json)" = 'available'
DOWNLOAD_URL=$(jq -er '.data.delivery.url' delivery.json)
curl --fail --silent --show-error --location \
--proto '=https' --proto-redir '=https' \
"$DOWNLOAD_URL" --output "$COLLECTION-output.mp4"Play the downloaded output, compare the Voiceover's pages with the original, and review narration, captions and illustrated scenes. Keep download URLs private; retrieve a fresh delivery response if a URL stops working.
Reruns, errors and costs
- Generation create/retry keys accept 1 to 255 printable ASCII characters
without spaces. Within 24 hours, replay the same create JSON with the same
key after a lost or uncertain response. Changing inputs with that key
returns
409. After the protection expires, resending can create and charge a new job, so retrieve the saved product ID first. - On an unresolved-acceptance
409, check the resource ID in the error. Do not switch to a fresh create key while the original may be running. If a rejection includesrecovery.idempotency_key, followreuseornewonly for a request that actually sent a key. - Correct invalid input, ownership, unsupported media, authentication or
subscription errors before retrying. For
429, wait forRetry-After. For a transient network/server error, reconcile the resource or replay the identical keyed request. Never automatically repeat an unkeyed write. - To deliberately retry a
failedor unstartedidleproduct, sendPOST /voiceovers/{id}/retryorPOST /videos/{id}/retrywith a newIdempotency-Key. The resource ID stays the same, but a new generation attempt starts. Check the error and balance first. There is no public cancellation or product deletion endpoint in these collections. - Current generation rates are 30 credits per narration minute, 7 per generated image and 20 per generated video clip. Voiceover uses its uploaded visuals and incurs no generated-visual credits. Estimates reserve credits; actual generated usage settles the hold, including usage from failed runs. Status and download calls do not generate content. See credit costs and holds.
- Duration and access limits depend on the plan. Read product options and the
account response, and inspect admission errors rather than assume every
preset is available. General API rate limits are organization-wide and
reported in
RateLimit-Limit,RateLimit-RemainingandRateLimit-Reset. The product guide describes the response contracts.
After verification, remove local request, response and media files according
to your data policy. Unused sources can be deleted with
DELETE /sources/{source_id}; do not remove a source still needed for ongoing
generation or future edits. Manage generated products in Studio.
Was this page helpful?