Skip to main content
The Uploads API provides direct file upload capabilities for video content. Use this when you have video files to upload directly rather than from a URL.

Upload methods

MethodBest ForMax Size
Direct uploadSmall files (< 100MB)100MB
Resumable uploadLarge files, unreliable networks5GB
URL-basedFiles already hosted elsewhereN/A

Direct upload

Simple multipart upload for smaller files.
POST /v1/content/upload

Request

Send as multipart/form-data:
file
file
required
Video file to upload.
title
string
required
Display title.
description
string
Optional description.
tags
string
Comma-separated tags.
metadata
string
JSON-encoded metadata object.
visibility
string
default:"private"
Initial visibility.

Example request

curl -X POST https://api.shortkit.dev/v1/content/upload \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  -F "[email protected]" \
  -F "title=My Video" \
  -F "tags=demo,tutorial" \
  -F 'metadata={"creator": "team-a"}'

Response

{
  "data": {
    "id": "cnt_abc123",
    "title": "My Video",
    "status": "processing",
    "uploadedBytes": 15728640,
    "createdAt": "2024-02-04T12:00:00Z"
  },
  "meta": {
    "requestId": "req_xyz789"
  }
}

Idempotency

Use the Idempotency-Key header to safely retry uploads:
curl -X POST https://api.shortkit.dev/v1/content/upload \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  -H "Idempotency-Key: unique-upload-id-123" \
  -F "[email protected]" \
  -F "title=My Video"

Resumable upload

For large files or unreliable networks, use resumable uploads.

Step 1: Initialize upload

POST /v1/uploads
filename
string
required
Original filename.
fileSize
integer
required
Total file size in bytes.
mimeType
string
required
MIME type (e.g., video/mp4).
title
string
required
Content title.
metadata
object
Content metadata.
curl -X POST https://api.shortkit.dev/v1/uploads \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "large-video.mp4",
    "fileSize": 524288000,
    "mimeType": "video/mp4",
    "title": "Large Video Upload"
  }'
Response:
{
  "data": {
    "uploadId": "upl_abc123",
    "uploadUrl": "https://upload.shortkit.dev/v1/uploads/upl_abc123",
    "expiresAt": "2024-02-05T12:00:00Z",
    "partSize": 10485760
  }
}

Step 2: Upload parts

Upload file in chunks using the provided uploadUrl:
# Upload part 1
curl -X PUT "https://upload.shortkit.dev/v1/uploads/upl_abc123?partNumber=1" \
  -H "Content-Type: application/octet-stream" \
  -H "Content-Length: 10485760" \
  --data-binary @part1.bin

# Upload part 2
curl -X PUT "https://upload.shortkit.dev/v1/uploads/upl_abc123?partNumber=2" \
  -H "Content-Type: application/octet-stream" \
  -H "Content-Length: 10485760" \
  --data-binary @part2.bin
Part response:
{
  "data": {
    "partNumber": 1,
    "etag": "\"abc123def456\"",
    "size": 10485760
  }
}

Step 3: Check upload status

GET /v1/uploads/{uploadId}
curl https://api.shortkit.dev/v1/uploads/upl_abc123 \
  -H "Authorization: Bearer sk_live_your_secret_key"
Response:
{
  "data": {
    "uploadId": "upl_abc123",
    "status": "in_progress",
    "uploadedBytes": 104857600,
    "totalBytes": 524288000,
    "parts": [
      { "partNumber": 1, "etag": "\"abc123\"", "size": 10485760 },
      { "partNumber": 2, "etag": "\"def456\"", "size": 10485760 }
    ]
  }
}

Step 4: Complete upload

POST /v1/uploads/{uploadId}/complete
curl -X POST https://api.shortkit.dev/v1/uploads/upl_abc123/complete \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "parts": [
      { "partNumber": 1, "etag": "\"abc123\"" },
      { "partNumber": 2, "etag": "\"def456\"" }
    ]
  }'
Response:
{
  "data": {
    "contentId": "cnt_xyz789",
    "status": "processing"
  }
}

Resuming interrupted uploads

If an upload is interrupted, query the upload status and resume from the last successful part:
async function resumeUpload(uploadId, file) {
  // Get current status
  const status = await getUploadStatus(uploadId);
  const uploadedParts = status.parts.map(p => p.partNumber);

  // Find missing parts
  const totalParts = Math.ceil(file.size / PART_SIZE);
  for (let i = 1; i <= totalParts; i++) {
    if (!uploadedParts.includes(i)) {
      await uploadPart(uploadId, file, i);
    }
  }

  // Complete upload
  await completeUpload(uploadId);
}

Abort upload

Cancel an incomplete upload:
DELETE /v1/uploads/{uploadId}
curl -X DELETE https://api.shortkit.dev/v1/uploads/upl_abc123 \
  -H "Authorization: Bearer sk_live_your_secret_key"

URL-based upload

Create content from an existing URL (see Content API):
curl -X POST https://api.shortkit.dev/v1/content \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceUrl": "https://storage.example.com/video.mp4",
    "title": "My Video"
  }'

Supported formats

Video formats

FormatExtensionsMIME Types
MP4.mp4, .m4vvideo/mp4
QuickTime.movvideo/quicktime
WebM.webmvideo/webm
MKV.mkvvideo/x-matroska

Video requirements

PropertyRequirement
Duration1-180 seconds
Resolution480p - 4K
Frame rate24-60 fps
BitrateUp to 50 Mbps
CodecH.264, H.265, VP9, AV1
Files not meeting these requirements may fail processing. Check content status after upload.

Upload SDK helpers

SDKs provide upload helpers with progress tracking:
import { ShortkitAdmin } from '@shortkit/node';

const shortkit = new ShortkitAdmin({ apiKey: 'sk_live_...' });

// Simple upload
const content = await shortkit.content.upload({
  file: fs.createReadStream('video.mp4'),
  title: 'My Video',
  onProgress: (progress) => {
    console.log(`${progress.percent}% uploaded`);
  }
});

// Resumable upload
const upload = shortkit.content.createResumableUpload({
  file: fs.createReadStream('large-video.mp4'),
  title: 'Large Video'
});

upload.on('progress', (progress) => {
  console.log(`${progress.uploadedBytes} / ${progress.totalBytes}`);
});

upload.on('complete', (content) => {
  console.log(`Created: ${content.id}`);
});

await upload.start();

Error handling

Common upload errors

Error CodeDescriptionSolution
FILE_TOO_LARGEFile exceeds size limitUse resumable upload
INVALID_MEDIA_TYPEUnsupported formatCheck supported formats
DURATION_EXCEEDEDVideo too longTrim to under 180 seconds
UPLOAD_EXPIREDResumable upload expiredStart new upload
PART_SIZE_MISMATCHPart size incorrectUse specified part size

Handling failures

try {
  const content = await shortkit.content.upload({ file, title });
} catch (error) {
  switch (error.code) {
    case 'FILE_TOO_LARGE':
      // Switch to resumable upload
      break;
    case 'INVALID_MEDIA_TYPE':
      // Show format error to user
      break;
    case 'UPLOAD_EXPIRED':
      // Restart upload
      break;
  }
}

Next steps