tscaps.docs
Documentation

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:

  1. Request an upload URL from the tscaps API.
  2. PUT the binary file directly into tscaps object storage using the presigned URL.
  3. Queue the job by passing the returned uploadId instead of a videoUrl.

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 HTTP PUT request.
  • 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-Length must match sizeBytes exactly. The byte length is cryptographically signed; a different size will be rejected by storage with HTTP 403.
  • Content-Type should match the returned contentType (for example video/mp4).
  • Do not send your API key: uploadUrl is a presigned URL that writes directly to object storage. Do not include an Authorization header, 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.