Direct file uploads
Upload video files directly to cloud storage when they are not hosted at a public URL.
When your video files are stored locally, behind a private firewall, or uploaded directly by your end-users, you can upload them to tscaps using presigned direct uploads rather than hosting them on a public web server.
Direct uploads use a secure three-step flow:
- Request an upload URL from the tscaps API.
- PUT the binary file directly into tscaps object storage using the presigned URL.
- Queue the job by passing the returned
uploadIdinstead of avideoUrl.
Step 1: Request an upload URL
Call POST /v1/automation-jobs/uploads with the name of the file and its exact size in bytes.
curl -X POST https://api.tscaps.io/v1/automation-jobs/uploads \
-H "Authorization: Bearer $TSCAPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileName": "reel.mp4",
"sizeBytes": 14205000
}'
const response = await fetch("https://api.tscaps.io/v1/automation-jobs/uploads", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TSCAPS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
fileName: "reel.mp4",
sizeBytes: 14205000,
}),
});
const upload = await response.json();
import os
import requests
response = requests.post(
"https://api.tscaps.io/v1/automation-jobs/uploads",
headers={"Authorization": f"Bearer {os.environ['TSCAPS_API_KEY']}"},
json={
"fileName": "reel.mp4",
"sizeBytes": 14205000,
},
)
upload = response.json()
Response
{
"uploadId": "3b71d39a-7c22-48a3-92f1-47fae3223019",
"uploadUrl": "https://storage.tscaps.io/uploads/3b71d39a-...?signature=...",
"contentType": "video/mp4",
"sizeBytes": 14205000,
"expiresAt": "2026-09-12T10:26:38.000Z"
}
The response provides:
uploadId: The reference ID used in Step 3 to create the job.uploadUrl: The signed target URL for your HTTPPUTrequest.contentType: The derived MIME type (e.g.video/mp4).sizeBytes: The expected payload byte size.expiresAt: Timestamp when the upload URL expires (valid for 15 minutes).
Step 2: Upload the video file
Send an HTTP PUT request directly to uploadUrl with the raw file bytes.
Important upload rules
Content-Lengthmust matchsizeBytesexactly. The byte length is cryptographically signed; a different size will be rejected by storage with HTTP 403.Content-Typeshould match the returnedcontentType(for examplevideo/mp4).- Do not send your API key:
uploadUrlis a presigned URL that writes directly to object storage. Do not include anAuthorizationheader, to avoid leaking your secret API key to third-party storage logs.- Send raw binary bytes: Send the raw video file directly in the request body. Do not wrap the file in
multipart/form-data(FormData), because storage would save the multipart boundary markers into the file and corrupt the video container.
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: video/mp4" \
-H "Content-Length: 14205000" \
--data-binary @reel.mp4
// Browser or Node.js (with File, Blob, or ReadStream)
await fetch(upload.uploadUrl, {
method: "PUT",
headers: {
"Content-Type": upload.contentType,
},
body: fileBlob,
});
with open("reel.mp4", "rb") as file:
response = requests.put(
upload["uploadUrl"],
headers={"Content-Type": upload["contentType"]},
data=file,
)
response.raise_for_status()
A successful upload responds with HTTP 200 OK.
Step 3: Queue the job
Once the binary upload finishes, start the job by sending uploadId in place of videoUrl:
curl -X POST https://api.tscaps.io/v1/automation-jobs \
-H "Authorization: Bearer $TSCAPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"uploadId": "3b71d39a-7c22-48a3-92f1-47fae3223019",
"templateId": "mira"
}'
const response = await fetch("https://api.tscaps.io/v1/automation-jobs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TSCAPS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
uploadId: upload.uploadId,
templateId: "mira",
}),
});
const job = await response.json();
console.log(`Job queued with upload: ${job.id}`);
response = requests.post(
"https://api.tscaps.io/v1/automation-jobs",
headers={"Authorization": f"Bearer {os.environ['TSCAPS_API_KEY']}"},
json={
"uploadId": upload["uploadId"],
"templateId": "mira",
},
)
job = response.json()
print(f"Job queued with upload: {job['id']}")
Storage and lifecycle rules
- Upload URL expiration: Presigned upload URLs expire after 15 minutes. Start the PUT request promptly after minting the URL.
- Unclaimed uploads: Uploaded files that are not turned into an automation job within 24 hours are automatically swept from storage.
- Upload limits: Minting upload URLs follows your plan’s daily limit (10 per day on Free, 100 per day on Pro) to prevent storage abuse.