Skip to main content

Configure Videos

Set content and metadata for videos within a bundle. You can configure a single video by position or update multiple videos in one batch request.

Configure a Single Video

PUT /bundles/:id/videos/:position

Sets or updates the content and metadata for the video at the given position (1-indexed).

Patch Video Metadata

PATCH /bundles/:id/videos/:position

Update lightweight metadata fields (external_ref, name) on a video without re-uploading content or changing its status. Works at any point in the video's lifecycle (including published, accepted, finalized).

Use this endpoint to fix or set your own reference IDs after the fact — no need to re-send the full configuration.

Request Body

All fields are optional. At least one must be provided.

FieldTypeDescription
external_refstring | nullYour own reference ID for this video. Max 200 characters. Set to null to clear.
namestring | nullInternal name for the video. Max 200 characters. Set to null to clear.

Example

curl -X PATCH https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/1 \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"external_ref": "correct-campaign-id-42"
}'

Response

Returns the full video object (same shape as GET /bundles/:id/videos/:position).

{
"data": {
"id": "vid_xyz",
"position": 1,
"status": "published",
"external_ref": "correct-campaign-id-42",
"name": "Video 1",
"...": "..."
}
}

Error Responses

StatusCodeDescription
400VALIDATION_ERRORNo fields provided, or invalid values.
404BUNDLE_NOT_FOUNDBundle does not exist.
404VIDEO_NOT_FOUNDNo video exists at this position.
403BUNDLE_NOT_OWNEDBundle belongs to another user.

Configure Multiple Videos (Batch)

PUT /bundles/:id/videos/batch

Updates multiple videos in a single request. The request body must be wrapped in a videos object — it is not a raw array.

{
"videos": [
{ "position": 1, "video_type": "video", "..." : "..." },
{ "position": 2, "video_type": "carousel", "..." : "..." }
]
}

auto_publish — configure and publish in one call

Both PUT /bundles/:id/videos/:position and PUT /bundles/:id/videos/batch accept an optional top-level boolean auto_publish (default false). When true, the API will attempt POST /bundles/:id/publish immediately after a successful video configuration. A common integration mistake is to configure videos and then forget to publish — auto_publish: true removes that step.

For POST /bundles/:id/videos/import-csv (multipart form), pass auto_publish=true as a separate form field alongside file.

Behaviour

  • The configuration step is the source of truth: if it succeeds, the response is a success response. auto_publish failures never fail the request.
  • If publish is not currently possible (account not yet configured, missing fields…), the response includes a blockers array — same codes as GET /bundles/:id/publish-readiness.
  • If publish was attempted and succeeded, the bundle moves to published (or published_priority) and a notification is created, identical to a manual POST /bundles/:id/publish.

auto_publish response object

Every video-configuration response now includes an auto_publish field:

{
"auto_publish": {
"attempted": true,
"published": true,
"bundle_status": "published",
"videos_published": 5
}
}
FieldTypeDescription
attemptedbooleantrue only if auto_publish: true was sent and at least one video was successfully configured in this call.
publishedbooleantrue if publish succeeded.
bundle_statusstringNew bundle status (only present when published: true).
videos_publishedintegerNumber of configured videos included in the publish (only when published: true).
blockersarrayPresent when published: false because publish-readiness checks failed. Each item: { code, message, details? }. See Publish-Readiness for the code list.
errorobjectPresent when publish was attempted but failed for a non-readiness reason (e.g., transient RPC failure). Shape: { code, message }. Re-attempt by calling POST /bundles/:id/publish directly.

Example — configure batch and publish

curl -X PUT https://app.tokportal.com/api/ext/bundles/bnd_abc123/videos/batch \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"auto_publish": true,
"videos": [
{ "position": 1, "video_type": "video", "video_url": "https://...", "description": "...", "target_publish_date": "2026-05-01" }
]
}'

Example — auto_publish skipped because the account is not ready

{
"data": {
"bundle_id": "bnd_abc123",
"configured": 5,
"results": [...],
"auto_publish": {
"attempted": true,
"published": false,
"blockers": [
{
"code": "ACCOUNT_MISSING_FIELDS",
"message": "Account is missing required fields.",
"details": { "missing_fields": ["profile_picture_url"] }
}
]
}
}
}

Field Reference

Common Fields

FieldTypeRequiredDescription
positionintegerBatch only1-indexed video position. Required in batch requests.
video_typestringYes"video" or "carousel". For Instagram: carousel with instagram_content_type: "reel" = Fixed Photos (creates a video from images), carousel with instagram_content_type: "post" = swipeable Carousel.
descriptionstringYesCaption or description for the post.
target_publish_datestringYesDesired publish date (YYYY-MM-DD). Min 3 days ahead for new accounts, 1 day for existing.
video_urlstringConditionalVideo file URL. Required when video_type is "video". You can pass a public_url from the upload endpoint or any external URL (Google Drive, Dropbox, direct link…) — the API will automatically download and re-upload the file to our storage.
carousel_imagesstring[]ConditionalArray of image storage paths. Required when video_type is "carousel". Use the storage_path from the image upload response.
tiktok_sound_urlstringConditionalTikTok sound URL. Required for TikTok carousels, optional for TikTok videos.
volume_original_soundintegerNoVolume of the original video sound, from 0 to 200 (percent). 100 = unchanged. Setting this for the first time on a video costs 1 credit (see note below).
volume_added_soundintegerNoVolume of the added sound/music track, from 0 to 200 (percent). 100 = unchanged. Setting this for the first time on a video costs 1 credit (see note below).
editing_instructionsstringNoFree-text instructions for the editor (e.g., "Add logo at end").
external_refstringNoYour own reference ID for this video (e.g., campaign or CMS identifier).
profile_picture_urlstringNoStorage path for the account profile picture. Use the storage_path from the image upload response.
Sound Volume Control — 1 credit per video

Setting volume_original_sound or volume_added_sound on a video enables the sound-volume-control feature for that video and costs 1 credit, debited automatically the first time either field is set.

  • Both fields accept integers from 0 (mute) to 200 (double volume). 100 means the sound is untouched.
  • The credit is charged once per video. Subsequent updates to the same fields on the same video are free.
  • If your balance is too low, the API returns INSUFFICIENT_CREDITS with feature: "sound_volume".
  • Supported on TikTok and Instagram.

Instagram-Specific Fields

FieldTypeRequiredDescription
instagram_content_typestringYes (Instagram)"reel" or "post". Required for Instagram bundles.
instagram_locationstringNoLocation tag for the Instagram post.
instagram_collaboratorsstring[]NoArray of Instagram usernames to tag as collaborators.
instagram_audio_namestringNoName of the audio track for Instagram Reels.
instagram_add_to_storybooleanNoIf true, the creator also shares the post to their Instagram Story.

Instagram Content Type Combinations

instagram_content_typevideo_typeResult
reelvideoReel Video
reelcarouselReel Fixed Photos — creates a video from images (not swipeable)
postvideoPost Video
postcarouselPost Carousel — swipeable photo carousel

YouTube Fields (Coming Soon)

YouTube support is not yet available. The following fields are reserved for future use:

FieldTypeDescription
youtube_titlestringVideo title.
youtube_tagsstring[]Array of tags.
youtube_categorystringVideo category.
youtube_visibilitystring"public", "unlisted", or "private".

Examples

TikTok Video

curl -X PUT https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/1 \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"video_type": "video",
"description": "Check out this new product! #ad #sponsored",
"target_publish_date": "2026-03-15",
"video_url": "https://pub-xxx.r2.dev/videos/bundle-id/promo.mp4",
"tiktok_sound_url": "https://www.tiktok.com/music/trending-sound-789",
"editing_instructions": "Add brand watermark in bottom-right corner",
"external_ref": "campaign-42-v1"
}'

Response:

{
"data": {
"position": 1,
"video_type": "video",
"status": "configured",
"description": "Check out this new product! #ad #sponsored",
"target_publish_date": "2026-03-15",
"video_url": "https://pub-xxx.r2.dev/videos/bundle-id/promo.mp4",
"tiktok_sound_url": "https://www.tiktok.com/music/trending-sound-789",
"editing_instructions": "Add brand watermark in bottom-right corner",
"external_ref": "campaign-42-v1"
}
}
curl -X PUT https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/2 \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"video_type": "carousel",
"description": "Swipe through our latest collection",
"target_publish_date": "2026-03-17",
"carousel_images": [
"carousel-images/org_xxx/bundle_abc123/slide1.jpg",
"carousel-images/org_xxx/bundle_abc123/slide2.jpg",
"carousel-images/org_xxx/bundle_abc123/slide3.jpg"
],
"tiktok_sound_url": "https://www.tiktok.com/music/chill-vibes-456",
"external_ref": "campaign-42-v2"
}'

Response:

{
"data": {
"position": 2,
"video_type": "carousel",
"status": "configured",
"description": "Swipe through our latest collection",
"target_publish_date": "2026-03-17",
"carousel_images": [
"carousel-images/org_xxx/bundle_abc123/slide1.jpg",
"carousel-images/org_xxx/bundle_abc123/slide2.jpg",
"carousel-images/org_xxx/bundle_abc123/slide3.jpg"
],
"tiktok_sound_url": "https://www.tiktok.com/music/chill-vibes-456",
"external_ref": "campaign-42-v2"
}
}

Instagram Reel (Video)

curl -X PUT https://app.tokportal.com/api/ext/bundles/bundle_def456/videos/1 \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"video_type": "video",
"instagram_content_type": "reel",
"description": "Morning routine with our skincare line",
"target_publish_date": "2026-03-20",
"video_url": "https://pub-xxx.r2.dev/videos/bundle-id/reel-skincare.mp4",
"instagram_location": "Los Angeles, California",
"instagram_collaborators": ["brandofficial"],
"instagram_audio_name": "Original Audio",
"instagram_add_to_story": true,
"external_ref": "ig-reel-001"
}'

Instagram Reel Fixed Photos (creates a video from images, not swipeable)

curl -X PUT https://app.tokportal.com/api/ext/bundles/bundle_def456/videos/2 \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"video_type": "carousel",
"instagram_content_type": "reel",
"description": "Style inspiration for spring",
"target_publish_date": "2026-03-20",
"carousel_images": [
"carousel-images/org_xxx/bundle_def456/slide1.jpg",
"carousel-images/org_xxx/bundle_def456/slide2.jpg"
],
"instagram_location": "Paris, France",
"instagram_collaborators": ["partner"],
"instagram_add_to_story": true
}'

Instagram Post Carousel (swipeable photo carousel)

curl -X PUT https://app.tokportal.com/api/ext/bundles/bundle_def456/videos/3 \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"video_type": "carousel",
"instagram_content_type": "post",
"description": "Our latest collection",
"target_publish_date": "2026-03-20",
"carousel_images": [
"carousel-images/org_xxx/bundle_def456/photo1.jpg",
"carousel-images/org_xxx/bundle_def456/photo2.jpg"
],
"instagram_location": "Paris, France"
}'
Instagram Carousel Behavior

For Instagram, video_type: "carousel" behaves differently depending on instagram_content_type:

  • With "reel": creates a video from your images (Fixed Photos — not swipeable)
  • With "post": creates a swipeable photo carousel

TikTok Video with Sound Volume Control

Lower the original video sound and boost the added music. Costs 1 extra credit (charged once per video).

curl -X PUT https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/1 \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"video_type": "video",
"description": "New product launch #ad",
"target_publish_date": "2026-03-15",
"video_url": "https://pub-xxx.r2.dev/videos/bundle-id/promo.mp4",
"tiktok_sound_url": "https://www.tiktok.com/music/trending-sound-789",
"volume_original_sound": 20,
"volume_added_sound": 150
}'

If your balance is below 1 credit:

{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Not enough credits to complete this operation.",
"details": {
"reason": "Need 1 credit to enable sound volume control.",
"feature": "sound_volume"
}
}
}

Automatic Video Download

You don't need to upload your video first. If you pass any external URL (Google Drive, Dropbox, any direct link) as video_url, the API will automatically download the file and store it on our servers. The returned video_url in the response will be the final hosted URL. This also works in batch configuration and CSV import.

URLs already hosted on our storage (pub-0d3f...r2.dev) are kept as-is (no redundant re-upload).


Batch Configuration

Configure multiple videos in a single request. The body must be wrapped in { "videos": [...] }.

curl -X PUT https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/batch \
-H "X-API-Key: tok_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"videos": [
{
"position": 1,
"video_type": "video",
"description": "Day 1 of the challenge #ad",
"target_publish_date": "2026-03-15",
"video_url": "https://pub-xxx.r2.dev/videos/bundle-id/day1.mp4"
},
{
"position": 2,
"video_type": "video",
"description": "Day 2 — things are getting interesting #ad",
"target_publish_date": "2026-03-18",
"video_url": "https://pub-xxx.r2.dev/videos/bundle-id/day2.mp4",
"tiktok_sound_url": "https://www.tiktok.com/music/hype-beat-101"
},
{
"position": 3,
"video_type": "carousel",
"description": "Final results — swipe to see the transformation",
"target_publish_date": "2026-03-21",
"carousel_images": [
"carousel-images/org_xxx/bundle_abc123/before.jpg",
"carousel-images/org_xxx/bundle_abc123/after.jpg"
],
"tiktok_sound_url": "https://www.tiktok.com/music/reveal-sound-202"
}
]
}'

Response:

{
"data": {
"bundle_id": "bundle_abc123",
"configured": 3,
"errors": [],
"results": [
{
"position": 1,
"video_type": "video",
"status": "configured"
},
{
"position": 2,
"video_type": "video",
"status": "configured"
},
{
"position": 3,
"video_type": "carousel",
"status": "configured"
}
]
}
}

If some videos fail validation, the response includes partial results:

{
"data": {
"bundle_id": "bundle_abc123",
"configured": 2,
"errors": [
{
"position": 3,
"error": "carousel_images is required when video_type is carousel"
}
],
"results": [
{
"position": 1,
"video_type": "video",
"status": "configured"
},
{
"position": 2,
"video_type": "video",
"status": "configured"
}
]
}
}

Date Validation

The target_publish_date must meet the following minimum lead times:

Account StatusMinimum Lead Time
New (not yet delivered)3 days from today
Existing (already delivered)1 day from today

If the date is too soon, the API returns a VALIDATION_ERROR:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "target_publish_date must be at least 3 days from today for new accounts.",
"details": {
"field": "target_publish_date",
"minimum_date": "2026-02-14"
}
}
}

Important: Media URL Types

When referencing uploaded files in video configuration:

  • video_url — Use the public_url from the video upload response.
  • carousel_images — Use the storage_path from the image upload response (not public_url).
  • profile_picture_url — Use the storage_path from the image upload response (not public_url).

See Media Upload for details on uploading files and obtaining URLs.