Endpoints

Slideshow API

POST /v1/slideshow turns 1 to 100 photos into an MP4 up to 15 minutes long. Add a voiceover from an audio file or from text read by one of 40 voices, background music that ducks under the voice, and word-by-word captions.

Pick a size, a transition and the motion, and the render comes back in the same call for most slideshows under a minute.

POST/v1/slideshow
Typical render
13 to 42 s
7 s to 42 s videos in our tests
Price
$0.02
per output minute, voiceover $0.05 per 1,000 characters
Length
Up to 15 minutes
1 to 100 images

Example#

Three photos, a voice reading one line and karaoke captions. Paste it as is: the photo URLs are public, and the video is ready in about 13 seconds.

curl -sS "https://api.reelsdk.com/v1/slideshow" \
  -H "Authorization: Bearer $REELSDK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "images": [
      "https://picsum.photos/id/1015/1920/1080",
      "https://picsum.photos/id/1018/1920/1080",
      "https://picsum.photos/id/1036/1920/1080"
    ],
    "size": "16:9",
    "voiceover": {
      "text": "Three days by the water. Mountains at sunrise, forests by noon, and a lake that held the whole sky.",
      "voice": "luna"
    },
    "captions": "karaoke"
  }'

Parameters#

Send a JSON body. Only images is required. Fields we don't recognize are ignored.

ParameterDescription
imagesarrayRequired

The photos, in order. Each item is a public image URL, or { url, seconds } to give that image its own length.

1 to 100 items

images[].urlstring

JPEG, PNG, WebP, AVIF or any image ffmpeg reads, up to 100 megapixels and 500 MB.

Public http(s) URL

images[].secondsnumber

How long this image stays on screen.

0.5 to 60

sizestring

Output size, such as 1080x1920 or an aspect like 9:16 or 16:9. See Sizes.

Default 1080x1920WIDTHxHEIGHT or an aspect name

widthinteger

Output width in pixels, as an alternative to size. Odd values are rounded to an even number.

240 to 2160, send with height

heightinteger

Output height in pixels.

240 to 2160, send with width

fpsinteger

Frame rate of the video.

Default 3024 to 60

seconds_per_imagenumber

Length of every image without its own seconds. Defaults to 3 without a voiceover. With a voiceover, those images share its length. See Image timing.

0.5 to 60

transitionstring or object

A transition name, random, or none. Send { type, seconds } to change its length. See Transitions and motion.

Default fade

transition.typestring

One of 58 transition names, random or none.

transition.secondsnumber

Length of each transition.

Default 0.50.1 to 2

fitstring

cover crops each photo to fill the frame. contain shows the whole photo over a blurred, darkened copy of itself.

coverdefaultcontain
motionstring or boolean

kenburns adds a slow pan and zoom to every photo. none or false keeps them still.

kenburnsdefaultnone
voiceoverstring or object

Narration: an audio URL, { url, volume }, or { text, voice, volume } to have the text read aloud.

voiceover.urlstring

An audio file, or a video with an audio track, up to 15 minutes.

Public http(s) URL

voiceover.textstring

Text to read aloud. Use this instead of url.

1 to 10,000 characters

voiceover.voicestring

One of 40 English voices.

Default luna

Show all 40 voices
amaltheaandromedaapolloarcasariesasteriaathenaatlasauroracallistacoracordeliadeliadracoelectraharmoniahelenaherahermeshyperionirisjanusjunojupiterlunamarsminervaneptuneodysseusopheliaorionorpheuspandoraphoebeplutosaturnthaliatheiavestazeus
voiceover.volumenumber

Voice level, where 1 is unchanged.

Default 10 to 2

musicstring or object

Background music: an audio URL, or { url, volume }. It loops to fill the video.

music.urlstring

An audio file up to 15 minutes.

Public http(s) URL

music.volumenumber

Music level before ducking.

Default 0.20 to 2

captionsboolean, string or object

Word-by-word captions of the voiceover: true for the bold preset, a preset name, or a style object. Needs a voiceover.

languagestring

Language of the voiceover for captions. Detected when you leave it out.

A code like en or pt-BR

qualitystring

H.264 encoder quality: CRF 26, 21 or 18.

draftstandarddefaulthigh
webhook_urlstring

We POST the finished job to this URL. See Webhooks.

Public http(s) URL

Sizes#

Set size to an aspect name or exact pixels like 1280x720. Without a size you get a vertical 1080x1920 video. Width and height must be 240 to 2160 pixels, and odd values are rounded to an even number.

sizePixelsGood for
9:16, portrait, vertical1080x1920Reels, TikTok, YouTube Shorts
16:9, landscape, horizontal1920x1080YouTube, websites
1:1, square1080x1080Feeds
4:51080x1350Instagram feed
3:41080x1440Portrait feeds
4:31440x1080Presentations

Image timing#

Images play back to back. Transitions are centered on the boundary between two images, so the video runs exactly the sum of the image lengths.

  • Without a voiceover, every image gets 3 seconds, or seconds_per_image if you set it.
  • With a voiceover, images without their own seconds share the voiceover length plus half a second, at least 1 second each. Three photos and a 6.7 s voiceover give 2.4 s per photo and a 7.2 s video.
  • An image with seconds always keeps that length.
  • The whole video can be up to 900 seconds. Longer timings return 400 before the job starts.
Request body with custom timing
{
  "images": [
    { "url": "https://example.com/cover.jpg", "seconds": 4 },
    "https://example.com/2.jpg",
    "https://example.com/3.jpg"
  ],
  "seconds_per_image": 2.5,
  "transition": {
    "type": "smoothleft",
    "seconds": 0.8
  },
  "music": {
    "url": "https://example.com/track.mp3",
    "volume": 0.3
  }
}

Here the first photo stays for 4 seconds and the other two for 2.5 seconds each, so the video is 9 seconds long.

Voiceover and music#

A voiceover can be any public audio file, or a video with an audio track. Send text instead and we read it aloud with one of 40 English voices. A text voiceover adds $0.05 per 1,000 characters, counted in usage.tts_characters.

Music loops until the video ends, fades out over the last second, and ducks under the voice while someone speaks. The final mix is normalized to -14 LUFS, the level most platforms play at, and encoded as AAC at 192 kbps. If the voiceover runs longer than your images, it fades out when the video ends.

Transitions and motion#

The default is a 0.5 second fade. random picks from 12 smooth ones, such as slides, covers and fades through black, and never uses the same one twice in a row. none cuts straight from one image to the next. Each transition lasts at most half of the shortest image.

All 58 transition names
fadefadeblackfadewhitefadegraysfadefastfadeslowdissolvedistancepixelizeradialwipeleftwiperightwipeupwipedownwipetlwipetrwipeblwipebrslideleftsliderightslideupslidedownsmoothleftsmoothrightsmoothupsmoothdowncirclecroprectcropcircleclosecircleopenhorzclosehorzopenvertclosevertopendiagbldiagbrdiagtldiagtrhlslicehrslicevuslicevdslicehblursqueezehsqueezevzoominhlwindhrwindvuwindvdwindcoverleftcoverrightcoverupcoverdownrevealleftrevealrightrevealuprevealdown

kenburns motion slowly zooms or pans every photo and changes the move from one photo to the next. Set motion to none for still photos.

Response#

A finished slideshow answers 200 with the job. outputs.video is your MP4. With captions on, you also get transcript, srt, vtt and ass, the same files as the Captions API. If the render outlasts your wait, you get 202 and a poll_url instead.

200 OK
{
  "job": {
    "id": "job_2q9jwcnzk4c85tuil3p1",
    "op": "slideshow",
    "status": "succeeded",
    "stage": null,
    "created_at": "2026-10-26T01:15:30.646Z",
    "finished_at": "2026-10-26T01:15:43.336Z",
    "outputs": {
      "video": "https://api.reelsdk.com/f/out/job_2q9jwcnzk4c85tuil3p1/video.mp4",
      "transcript": "https://api.reelsdk.com/f/out/job_2q9jwcnzk4c85tuil3p1/transcript.json",
      "srt": "https://api.reelsdk.com/f/out/job_2q9jwcnzk4c85tuil3p1/captions.srt",
      "vtt": "https://api.reelsdk.com/f/out/job_2q9jwcnzk4c85tuil3p1/captions.vtt",
      "ass": "https://api.reelsdk.com/f/out/job_2q9jwcnzk4c85tuil3p1/captions.ass"
    },
    "result": null,
    "duration_s": 7.2,
    "error": null,
    "usage": {
      "output_seconds": 7.2,
      "audio_seconds": 6.696,
      "tts_characters": 99,
      "images": 0
    },
    "poll_url": "https://api.reelsdk.com/v1/jobs/job_2q9jwcnzk4c85tuil3p1"
  }
}

usage.output_seconds is the video length you pay for, usage.tts_characters the characters read aloud, and usage.audio_seconds the voiceover we transcribed for captions. Files stay up for 7 days.

Errors#

Bad fields return 400 before a job starts. Problems with your files fail the job with 422. The full list is in Errors.

CodeHTTPWhen it happens
INVALID_REQUEST400A field is invalid, for example captions: captions need a voiceover to transcribe
INPUT_FETCH_FAILED422We couldn't download a file, for example Could not fetch images[2]: HTTP 404
INPUT_TOO_LARGE422A file is over 500 MB, or all files together are over 1,000 MB
INPUT_UNSUPPORTED422An image can't be decoded or is over 100 megapixels, or the audio has no sound or runs over 15 minutes
INVALID_SPEC422The voiceover would make the slideshow longer than 15 minutes
TTS_FAILED500Reading the text aloud failed after our automatic retry. Send the request again.

Tips#

  • Pick the size for where the video goes: 9:16 for Reels, TikTok and Shorts, 16:9 for YouTube, 1:1 or 4:5 for feeds.
  • Render time grows with the video length, close to real time for long slideshows. Past about a minute of video, raise ?wait or use a webhook.
  • Image URLs must be public. For local files, upload them with POST /v1/uploads and pass the returned URLs.
  • Use quality: "draft" while you try out timings and transitions.

Last updated 26 October 2026