Documentation
SlateSources
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:
PUTthe bytes toput_url, sending every header inheaders.If-None-Match: *is part of the signature: leave it out and object storage answers400. It is also what makes the slot single-use, so an uploaded object can never be replaced.- 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 isreadyorrejected.
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
ingestingfor more than two hours; - removes the storage left behind by failed and rejected sources and by abandoned multipart uploads.