Concepts
Uploads and files
POST /v1/uploads stores a local file of up to 100 MB and returns a URL you can use as an input to any job. Uploads and rendered outputs are served from public /f/ URLs for 7 days.
If your files already live at a public URL, skip the upload and pass that URL straight to the job.
- Per upload
- 100 MB
- Per key
- 20 GB a day
- resets at midnight UTC
- Kept for
- 7 days
- then the URL returns 404
Upload a file#
Send the raw file as the request body, with its name in the filename query parameter.
curl -sS "https://api.reelsdk.com/v1/uploads?filename=river.jpg" \
-H "Authorization: Bearer $REELSDK_KEY" \
--data-binary @river.jpgThe answer is 201 Created with the file's URL, its size, the content type we picked and when it expires.
{
"url": "https://api.reelsdk.com/f/in/fr78av0etm46huw3/river.jpg",
"bytes": 71069,
"content_type": "image/jpeg",
"expires_at": "2026-11-02T01:15:29.712Z"
}Pass url as any input: video_url for captions, an entry in images for a slideshow, a voiceover or music track, or a file in ffmpeg inputs.
Parameters#
| Parameter | Description |
|---|---|
filenamequery stringRequired | The file's name. Its extension sets the content type we serve the file with, and it becomes the last part of the URL. Ends in a supported extension |
Content-LengthheaderRequired | The size of the body in bytes. Most clients set it when you send a whole file. 1 byte to 100 MB |
bodyraw bytesRequired | The file itself, not JSON or multipart form data. |
We keep the last part of the name, swap anything other than letters, digits, ., - and _ for _, and cut it to 64 characters. My Trip (1).MOV becomes My_Trip_1_.MOV.
File types#
The extension decides the type. Anything else returns 400 UNSUPPORTED_FILE_TYPE.
| Kind | Extensions |
|---|---|
| Video | mp4, m4v, mov, webm, mkv, avi, ts |
| Image | jpg, jpeg, png, webp, avif, gif, bmp |
| Audio | mp3, m4a, aac, wav, ogg, opus, flac |
| Text | srt, vtt, ass, json, txt |
File URLs#
Uploads live under /f/in/ and job outputs under /f/out/. Both work without an API key, so you can put them in a video tag, hand them to a CDN or share them with your users.
GETandHEADare supported, withAccess-Control-Allow-Origin: *so browsers can fetch them.Rangerequests answer206 Partial Content, so players can seek and resume. A range past the end answers416.- Every file has an
ETag. Send it inIf-None-Matchand an unchanged file answers304. - Images, video and audio are served
inline, so they play in the browser. Subtitles, JSON and text download as attachments. Cache-Control: public, max-age=604800, immutable, since a file never changes once written.
curl -sS -r 0-99 -D - -o /dev/null \
"https://api.reelsdk.com/f/in/fr78av0etm46huw3/river.jpg"
# HTTP/2 206
# content-type: image/jpeg
# content-range: bytes 0-99/71069
# etag: "cc07182fe39b32813949820bce71df61"
# cache-control: public, max-age=604800, immutableRetention#
Uploads and outputs are deleted 7 days after they're written, and their URLs then return 404 with file not found or expired. The upload response tells you when in expires_at. Copy outputs you want to keep to your own storage, for example from your webhook handler.
Remote URLs#
Every input also takes any public http or https URL, including signed links from S3, R2, Google Cloud Storage or Supabase, as long as they stay valid until the job has downloaded them.
- URLs can be up to 4,096 characters.
localhost, private and reserved IP addresses and hostnames without a dot are refused with400. - We follow up to 5 redirects and try each download up to 3 times when the server answers
5xx,408or429or the connection drops. - A download that sends no data for 30 seconds counts as a failed try.
- Each file can be up to 500 MB, and all inputs of a job together up to 1,000 MB.
- Outputs of one job work as inputs to the next, so you can caption a slideshow without downloading it in between.
A URL that keeps failing ends the job with 422 INPUT_FETCH_FAILED and a message such as Could not fetch video_url: HTTP 404.
Errors#
| Code | HTTP | When it happens |
|---|---|---|
| INVALID_REQUEST | 400 | filename is missing (add ?filename=name.ext so we know the file type) or the body is empty |
| UNSUPPORTED_FILE_TYPE | 400 | The extension isn't in the list above |
| CONTENT_LENGTH_REQUIRED | 400 | The request had no usable Content-Length header |
| PAYLOAD_TOO_LARGE | 413 | The file is over 100 MB |
| RATE_LIMITED | 429 | The key used up its 20 GB of uploads for the day. Retry-After counts down to midnight UTC. |
Last updated 26 October 2026