API Reference

Upload

Upload files to your content library using a two-step presigned URL flow. Get a presigned URL, upload directly to storage, then finalize.

How It Works

Uploading follows a three-step pattern that keeps large files off the API server:

  1. Presign — request a presigned URL from the API
  2. Upload — PUT your file directly to that URL
  3. Complete — tell the API the upload is done to create the content record

Carousel uploads

For carousel content (2–10 images), repeat all three steps for each image. Use the contentItemId returned from the first complete call to attach additional images to the same item.

Content Type Rules

ParameterTypeDescription
shortformvideomp4, mov — max 300 MB, 3–90s duration, single file
carouselimagejpeg, png, webp — max 20 MB per image, 2–10 files
longformvideomp4, mov — max 1 GB, single file

Get Presigned URL

POST/api/v1/upload/presign

Request a presigned URL for uploading a file directly to storage.

Request body

ParameterTypeDescription
fileNamerequiredstringOriginal file name
contentTyperequiredstringMIME type (e.g. video/mp4, image/jpeg)
fileSizerequirednumberFile size in bytes
typestringshortform, carousel, or longform. Auto-detected from MIME type if omitted.
thumbnailbooleanSet true for thumbnail uploads. Skips content type validation and storage quota checks. Only image/jpeg, image/png, image/webp accepted.

Response

{
  "presignedUrl": "https://storage.example.com/...",
  "key": "userId/fileId/clip.mp4",
  "fileId": "550e8400-e29b-41d4-a716-446655440000",
  "type": "shortform"
}

Example

cURL
curl -X POST https://stashkit.net/api/v1/upload/presign \
  -H "Authorization: Bearer sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "clip.mp4",
    "contentType": "video/mp4",
    "fileSize": 12345678,
    "type": "shortform"
  }'

Upload the File

After receiving the presigned URL, upload your file directly to storage with a PUT request. Both the Content-Type and Content-Length headers must match the values you used in the presign step — the presigned URL is signed with these values and mismatched headers will result in a 403 error.

cURL
curl -X PUT "<presignedUrl>" \
  -H "Content-Type: video/mp4" \
  -H "Content-Length: 12345678" \
  --data-binary @clip.mp4

Presigned URL expiry

Presigned URLs expire after 1 hour. Start the upload promptly after requesting the URL.

Finalize Upload

POST/api/v1/upload/complete

Finalize the upload and create the content item in your library.

Request body

ParameterTypeDescription
keyrequiredstringThe key returned from the presign step
fileNamerequiredstringOriginal file name
contentTyperequiredstringMIME type
fileSizerequirednumberFile size in bytes
typerequiredstringshortform, carousel, or longform
durationSecondsnumberVideo duration in seconds (shortform / longform)
contentItemIdstringExisting content item ID — for adding images to a carousel

Response — new item

{
  "item": {
    "id": "uuid",
    "type": "shortform",
    "title": "clip",
    "status": "draft",
    "fileUrl": "https://...",
    "thumbnailUrl": null,
    ...
  },
  "contentItemId": "uuid"
}

Response — carousel addition

{
  "item": { ... },
  "file": {
    "id": "uuid",
    "fileKey": "...",
    "sortOrder": 2,
    ...
  },
  "contentItemId": "uuid"
}

Example

cURL
curl -X POST https://stashkit.net/api/v1/upload/complete \
  -H "Authorization: Bearer sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "userId/fileId/clip.mp4",
    "fileName": "clip.mp4",
    "contentType": "video/mp4",
    "fileSize": 12345678,
    "type": "shortform",
    "durationSeconds": 45
  }'

Upload a 3-image carousel by repeating the presign → upload → complete cycle. The first call creates the item; subsequent calls attach images using contentItemId.

Step 1 — First image (creates item)
# Presign
PRESIGN=$(curl -s -X POST https://stashkit.net/api/v1/upload/presign \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fileName":"slide1.jpg","contentType":"image/jpeg","fileSize":204800,"type":"carousel"}')

# Upload to storage
curl -X PUT "$(echo $PRESIGN | jq -r .presignedUrl)" \
  -H "Content-Type: image/jpeg" \
  --data-binary @slide1.jpg

# Complete — save the contentItemId
COMPLETE=$(curl -s -X POST https://stashkit.net/api/v1/upload/complete \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"key\": $(echo $PRESIGN | jq .key),
    \"fileName\": \"slide1.jpg\",
    \"contentType\": \"image/jpeg\",
    \"fileSize\": 204800,
    \"type\": \"carousel\"
  }")

ITEM_ID=$(echo $COMPLETE | jq -r .contentItemId)
Steps 2-3 — Additional images
# Repeat presign + upload for slide2.jpg, then complete with contentItemId:
curl -X POST https://stashkit.net/api/v1/upload/complete \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"key\": \"<key from presign>\",
    \"fileName\": \"slide2.jpg\",
    \"contentType\": \"image/jpeg\",
    \"fileSize\": 180000,
    \"type\": \"carousel\",
    \"contentItemId\": \"$ITEM_ID\"
  }"