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.

POST/v1/uploads?filename=
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.jpg

The answer is 201 Created with the file's URL, its size, the content type we picked and when it expires.

201 Created
{
  "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#

ParameterDescription
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.

KindExtensions
Videomp4, m4v, mov, webm, mkv, avi, ts
Imagejpg, jpeg, png, webp, avif, gif, bmp
Audiomp3, m4a, aac, wav, ogg, opus, flac
Textsrt, 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.

  • GET and HEAD are supported, with Access-Control-Allow-Origin: * so browsers can fetch them.
  • Range requests answer 206 Partial Content, so players can seek and resume. A range past the end answers 416.
  • Every file has an ETag. Send it in If-None-Match and an unchanged file answers 304.
  • 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.
Range request
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, immutable

Retention#

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 with 400.
  • We follow up to 5 redirects and try each download up to 3 times when the server answers 5xx, 408 or 429 or 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#

CodeHTTPWhen it happens
INVALID_REQUEST400filename is missing (add ?filename=name.ext so we know the file type) or the body is empty
UNSUPPORTED_FILE_TYPE400The extension isn't in the list above
CONTENT_LENGTH_REQUIRED400The request had no usable Content-Length header
PAYLOAD_TOO_LARGE413The file is over 100 MB
RATE_LIMITED429The key used up its 20 GB of uploads for the day. Retry-After counts down to midnight UTC.

Last updated 26 October 2026