Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Special early adopter pricing is available now.

← All documentation

Documentation

Slate

Sources

Updated

A source is one media file Slate can use in a recipe: an image, an audio track, a font or a video. Recipes never point at files or URLs directly. They reference source IDs, and a source must be ready before a run can use it.

There are two ways to create a source. Both use POST /api/v1/sources with exactly one of upload or url. Any other field gets 422 with reason invalid_source.

Direct upload

Declare the content type and the size in bytes:

{
  "upload": {
    "content_type": "audio/mpeg",
    "size": 72045
  }
}

Slate answers 201 with a single-use, signed upload slot in your bucket:

{
  "id": "c12a53df-9ec6-48e9-8a4a-1f75a82cee80",
  "put_url": "https://…?X-Amz-Signature=…",
  "headers": {"Host": ["…"], "If-None-Match": ["*"]},
  "expires_at": "2026-10-08T12:15:00.000000Z",
  "object_key": "slate-sources/c12a53df-9ec6-48e9-8a4a-1f75a82cee80/source"
}

Then:

  1. PUT the bytes to put_url, sending every header in headers. If-None-Match: * is part of the signature: leave it out and object storage answers 400. It is also what makes the slot single-use, so an uploaded object can never be replaced.
  2. Call GET /api/v1/sources/{id}. This call is what tells Slate the upload has arrived. Slate checks for the object and queues its validation only when you read the source. Keep polling until the status is ready or rejected.

The upload slot is valid for 15 minutes. Upload and make your first GET before expires_at. If you don't, the source never becomes ready and the hourly cleanup marks it failed.

The bytes go straight to your bucket. Slate never proxies them. object_key is for information only. Recipes always use the source id.

Content types

The declared type decides which checks apply:

Kind Accepted content_type
Video video/*
Audio audio/*
Still image image/png, image/jpeg
Font font/*, application/font-sfnt, application/vnd.ms-opentype, application/x-font-ttf

Slate checks the file's leading bytes rather than trusting the declaration.

Remote URL

Instead of uploading, give Slate an address to fetch:

{"url": "https://media.example/interview.mp4"}

Slate answers 202 with {"id": "…"} and fetches the file on its ingest queue. The URL must:

  • use HTTPS on port 443;
  • resolve to public addresses, not private or internal ones;
  • serve the file directly. Slate does not follow redirects.

Source status

GET /api/v1/sources/{id} returns:

{
  "id": "c12a53df-9ec6-48e9-8a4a-1f75a82cee80",
  "status": "ready",
  "rejection_reason": null,
  "bytes": 72045,
  "content_type": "audio/mpeg"
}
Status Meaning
pending Registered. For a direct upload, Slate is waiting for the bytes.
ingesting The bytes are being fetched or checked.
ready The source can be used in a recipe.
rejected The file failed a check. rejection_reason says which.
failed Ingest did not complete, for example because the upload slot expired.

A source that belongs to another credential's subject returns the same 404 (reason source_not_found) as one that does not exist.

What every source is checked for

Slate treats every source as potentially hostile. Before any rendering, each source is staged in an isolated scratch directory, its container structure is checked, and ffprobe measures it against these limits:

Limit Value
Size 8 GiB
Duration 4 hours
Width and height 4096 × 4096 pixels
Streams 8
Frame rate 60 fps
Codecs Slate will decode AAC, AV1, FLAC, H.264, HEVC, MJPEG, MP3, MPEG-4, Opus, PCM (16- and 24-bit), ProRes, Vorbis, VP8, VP9

Audio and video must be a self-contained MP4, MOV, M4A, WebM, Matroska, MP3, WAV or AAC file. HLS and DASH playlists, concat scripts, text files and QuickTime reference movies are rejected, because each of those could make ffmpeg open another resource.

The rejection reasons a source can carry are source_too_large, unsupported_container, referenced_media, probe_failed, duration_exceeded, resolution_exceeded, stream_count_exceeded, decode_codec_not_allowed, frame_rate_exceeded, video_stream_missing and audio_stream_missing.

These checks lower the risk of crafted media, but they do not prove a file is safe. A file that exploits a bug in ffmpeg would run on your queue worker. Apply your own upload limits and scanning before handing untrusted media to Slate, and upload it straight to storage rather than through your web tier.

Storage and cleanup

Ready sources stay in your bucket under slate-sources/. Slate has no scheduled job that deletes ready sources or finished results. The hourly slate:prune-ingests job only:

  • fails direct uploads whose slot expired while still pending;
  • fails sources stuck in ingesting for more than two hours;
  • removes the storage left behind by failed and rejected sources and by abandoned multipart uploads.