Skip to main content

Media Upload

Before configuring videos, you need to upload your media files (videos and images) to TokPortal's storage.

You have two options:

  • Direct upload (simpler) — Send the file directly to the API. Best for scripts, MCP, and automation.
  • Presigned URL (advanced) — Get a temporary upload URL and PUT the file yourself. Best for browser-based uploads.

Option 1: Direct Upload

Upload a file directly — the server handles everything.

Upload Video (Direct)

POST /upload/video/direct
Content-Type: multipart/form-data
curl -X POST https://app.tokportal.com/api/ext/upload/video/direct \
-H "X-API-Key: tok_live_xxx" \
-F "file=@/path/to/video.mp4" \
-F "bundle_id=your-bundle-id"

Response:

{
"data": {
"public_url": "https://pub-xxx.r2.dev/videos/bundle-id/123456.mp4",
"filename": "video.mp4",
"size_bytes": 15234567,
"content_type": "video/mp4"
}
}

Upload Image (Direct)

POST /upload/image/direct
Content-Type: multipart/form-data
curl -X POST https://app.tokportal.com/api/ext/upload/image/direct \
-H "X-API-Key: tok_live_xxx" \
-F "file=@/path/to/photo.jpg" \
-F "bundle_id=your-bundle-id" \
-F "purpose=carousel"

purpose can be carousel (default) or profile_picture.

Response:

{
"data": {
"public_url": "https://xxx.supabase.co/storage/v1/object/public/carousel-images/...",
"storage_path": "carousel-images/org_xxx/bundle-id/photo-123456.jpg",
"filename": "photo.jpg",
"size_bytes": 245678,
"content_type": "image/jpeg"
}
}

Using Upload Results in Video Configuration

Video Config FieldUse This Value
video_urlpublic_url from the video upload response
carousel_images[]storage_path from the image upload response
profile_picture_urlstorage_path from the image upload response

Important: For carousel_images and profile_picture_url, use storage_pathnot public_url. For video_url, use public_url.


Option 2: Presigned URL Upload

Get a temporary URL and upload the file yourself. This is useful for browser-based uploads where the client uploads directly.

Upload Flow

  1. Request a presigned URL — Call the upload endpoint with file metadata.
  2. Upload the file — Use the returned upload_url and method to upload the file.
  3. Use the URL in video config — Pass the appropriate URL to the video configuration endpoints.

Presigned Video Upload

POST /upload/video

Returns a presigned Cloudflare R2 URL for uploading a video file.

FieldTypeRequiredDescription
filenamestringYesOriginal filename including extension (e.g., promo.mp4).
content_typestringYesMIME type of the file (e.g., video/mp4).
bundle_idstringYesThe bundle this video belongs to.

Step 1 — Get the presigned URL

curl -X POST https://app.tokportal.com/api/ext/upload/video \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"filename": "promo.mp4",
"content_type": "video/mp4",
"bundle_id": "bnd_a1b2c3d4"
}'

Response:

{
"data": {
"upload_url": "https://storage.tokportal.com/videos/org_xxx/bnd_a1b2c3d4/promo-1707660000.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
"public_url": "https://pub-xxx.r2.dev/videos/org_xxx/bnd_a1b2c3d4/promo-1707660000.mp4",
"method": "PUT",
"content_type": "video/mp4",
"expires_in_seconds": 3600,
"instructions": "PUT the file to upload_url with the specified Content-Type header."
}
}

Step 2 — Upload the file

Use the method and upload_url from the response:

curl -X PUT "https://storage.tokportal.com/videos/org_xxx/bnd_a1b2c3d4/promo-1707660000.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&..." \
-H "Content-Type: video/mp4" \
--data-binary @promo.mp4

Step 3 — Use the public URL

Pass public_url as video_url when configuring a video:

{
"video_type": "video",
"video_url": "https://pub-xxx.r2.dev/videos/org_xxx/bnd_a1b2c3d4/promo-1707660000.mp4",
"description": "New product launch",
"target_publish_date": "2026-03-15"
}

Presigned Image Upload

POST /upload/image

Returns a presigned Supabase Storage URL for uploading an image. Use this for carousel slides and profile pictures.

FieldTypeRequiredDescription
filenamestringYesOriginal filename including extension (e.g., slide1.jpg).
content_typestringYesMIME type of the file (e.g., image/jpeg).
bundle_idstringYesThe bundle this image belongs to.
purposestringNo"carousel" (default) or "profile_picture".

Step 1 — Get the presigned URL

curl -X POST https://app.tokportal.com/api/ext/upload/image \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"filename": "slide1.jpg",
"content_type": "image/jpeg",
"bundle_id": "bnd_a1b2c3d4",
"purpose": "carousel"
}'

Response:

{
"data": {
"upload_url": "https://xxx.supabase.co/storage/v1/object/upload/sign/carousel-images/org_xxx/bnd_a1b2c3d4/slide1-1707660000.jpg?token=...",
"public_url": "https://xxx.supabase.co/storage/v1/object/public/carousel-images/org_xxx/bnd_a1b2c3d4/slide1-1707660000.jpg",
"storage_path": "carousel-images/org_xxx/bnd_a1b2c3d4/slide1-1707660000.jpg",
"method": "PUT",
"content_type": "image/jpeg",
"expires_in_seconds": 3600,
"instructions": "PUT the file to upload_url with the specified Content-Type header."
}
}

Step 2 — Upload the file

curl -X PUT "https://xxx.supabase.co/storage/v1/object/upload/sign/carousel-images/org_xxx/bnd_a1b2c3d4/slide1-1707660000.jpg?token=..." \
-H "Content-Type: image/jpeg" \
--data-binary @slide1.jpg

Step 3 — Use the storage path

For carousel_images and profile_picture_url, use the storage_path value (not public_url):

{
"video_type": "carousel",
"carousel_images": [
"carousel-images/org_xxx/bnd_a1b2c3d4/slide1-1707660000.jpg",
"carousel-images/org_xxx/bnd_a1b2c3d4/slide2-1707660000.jpg"
],
"description": "Swipe through our latest collection",
"target_publish_date": "2026-03-15"
}

Presigned Upload Response Fields

FieldTypeDescription
upload_urlstringThe temporary presigned URL to upload the file to.
public_urlstringThe permanent public URL for the file after upload.
storage_pathstringThe storage path (images only). Use this for carousel_images and profile_picture_url.
methodstringHTTP method to use for the upload (always PUT).
content_typestringThe MIME type to set in the upload Content-Type header.
expires_in_secondsintegerSeconds until the presigned URL expires (typically 3600).
instructionsstringHuman-readable instructions for completing the upload.

Accepted Formats

Video

FormatMIME TypeExtension
MP4video/mp4.mp4
MOVvideo/quicktime.mov
WebMvideo/webm.webm

Image

FormatMIME TypeExtension
JPEGimage/jpeg.jpg, .jpeg
PNGimage/png.png
WebPimage/webp.webp

Presigned URL Expiration

Presigned URLs expire after the time indicated in expires_in_seconds (typically 1 hour / 3600 seconds). If the URL expires before you upload, request a new one.

Error Handling

Unsupported file type:

{
"error": {
"code": "UNSUPPORTED_FILE_TYPE",
"message": "The content type 'video/avi' is not supported.",
"details": {
"supported_types": ["video/mp4", "video/quicktime", "video/webm"]
}
}
}

Missing required field:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "bundle_id is required."
}
}