When you compose a video edit, you normally provide URLs to your source footage and Shotstack downloads them as part of rendering. That works well, but pulling files from arbitrary URLs every time comes with some downsides — latency, bandwidth costs, and no easy way to standardize footage coming from different devices or sources.
The Ingest API solves this by letting you upload or fetch source files (video, image, audio and fonts) once, store them with Shotstack, and reuse them across edits — with optional transformations along the way.
Fetch a file from a URL
If your footage is already hosted somewhere, send Shotstack the URL and it will fetch it asynchronously:
curl -X POST https://api.shotstack.io/ingest/stage/sources \
-H 'Content-Type: application/json' \
-H 'x-api-key: YOUR_API_KEY' \
-d '{ "url": "https://example.com/video.mp4" }'
You'll get back a source id in the response:
{
"data": {
"type": "source",
"id": "zzytey4v-32km-kq1z-aftr-3kcuqi0brad2"
}
}
Use that id to check the file's status or reference it later.
Upload a file directly
If the file isn't hosted anywhere yet, you can upload it straight from your application in two steps: request a signed upload URL, then PUT your file to it. No storage or hosting setup required on your end.
Step 1 — Request a signed URL
curl -X POST https://api.shotstack.io/ingest/stage/upload \
-H 'Accept: application/json' \
-H 'x-api-key: YOUR_API_KEY'
The response includes a signed URL at data.attributes.url, valid for a limited time:
{
"data": {
"type": "upload",
"id": "zzyafe82-4tvb-ku1r-87ty-0io5sv0mtrdl",
"attributes": {
"id": "zzyafe82-4tvb-ku1r-87ty-0io5sv0mtrdl",
"url": "https://shotstack-ingest-api-v1-sources.s3.ap-southeast-2.amazonaws.com/.../source?AWSAccessKeyId=...&Expires=...&Signature=...",
"expires": "2023-01-02T02:47:37.260Z"
}
}
}
Step 2 — PUT your file to the signed URL
Upload the file directly to the signed URL using an HTTP PUT request:
curl -X PUT -T video.mp4 "SIGNED_URL"
Note: The upload runs in the foreground and shouldn't be interrupted until it completes. If it's interrupted, just re-run the command — the signed URL stays valid until it expires.
Once uploaded, poll the source's status (below) to confirm it's ready to use.
Setting the Content-Type header
For common media files (video, image, audio) the type is auto-detected, so a Content-Type header is optional. For text-based files like .srt, .vtt, .csv or .txt, auto-detection doesn't work — set the type explicitly one of two ways:
curl -X PUT -H 'Content-Type: application/x-subrip' -T subtitles.srt "SIGNED_URL"
…or provide a filename with the correct extension when requesting the signed URL instead:
curl -X POST https://api.shotstack.io/ingest/stage/upload \
-H 'Content-Type: application/json' \
-H 'x-api-key: YOUR_API_KEY' \
-d '{ "filename": "subtitles.srt" }'
Checking when a file is ready
Ingesting and processing can take a few seconds to a few minutes. You have two options:
Poll the source's status by
iduntil it showsready(alsoqueued,importing, orfailedif something goes wrong).Add a
callbackURL to your ingest request and receive a webhook to your own server when it's done — no polling needed.
"callback": "https://my-server.com/webhook.php"
Webhooks are more efficient than polling if you're ingesting at any real volume. See the Understanding Webhooks article for how the payload is structured.
Creating renditions
You can also ask Shotstack to transform a source file as part of ingestion — resizing, changing aspect ratio, cropping, or adjusting framerate and quality. This is useful for standardizing footage from different devices or matching your edit's output resolution. Add an outputs.renditions array to your ingest request:
curl -X POST https://api.shotstack.io/ingest/stage/sources \
-H 'Content-Type: application/json' \
-H 'x-api-key: YOUR_API_KEY' \
-d '{
"url": "https://example.com/video.mp4",
"outputs": {
"renditions": [ { "resolution": "hd" } ]
}
}'
Renditions also work with direct uploads — just include the same outputs.renditions array in your signed URL request instead. Check rendition status the same way as source files, under data.attributes.outputs.renditions in the response.
Note: Files ingested via the Ingest API are stored in S3 in the AWS Sydney region, primarily for use in your edits. We don't recommend serving these files directly to your end users — use a CDN or your own storage/hosting for that.
Source visibility and security
Uploaded and fetched sources are stored with a public-read ACL — the file is reachable by anyone who has its direct URL, for as long as the source exists. The source id embedded in that URL is cryptographically random and cannot be guessed or enumerated, so in practice the exposure is "anyone with the link", not "anyone who can guess an id".
If that level of security still isn't acceptable for your content, you have two options:
Delete the source as soon as its render completes (see below).
Skip the Ingest API entirely and pass source URLs directly in your render request, hosted wherever you like with whatever access controls you want. The only requirement is that the URL is reachable by our render engine at the moment the render runs, so a short-lived signed URL is fine as long as it hasn't expired by then.
Note that we cache fetched source files for 24 hours by default to save re-downloading them — add"cache": falseto your timeline object if you don't want us to hold a copy beyond the render itself.
Deleting a source
DELETE /sources/{id} removes both the original file and any renditions generated from it. Deletion is immediate, not queued — the files stop being accessible as soon as the call returns.
The source record itself remains queryable afterwards with a status of deleted, so the metadata stays as a record even though the media is gone.
File size limits
The Ingest API accepts files up to 5GB per file, with a 10GB combined limit for source assets plus output on any single render. This is a hard limit rather than an incidental side effect of the signed upload — there's currently no multipart, chunked, or resumable upload path above it.
If a source exceeds the limit, splitting it into two or more sources and placing them back-to-back on the timeline avoids the quality loss that comes with re-encoding at a lower bitrate.
