Videos

CSV Import – Bulk Video Upload from Spreadsheet

Bulk-import videos into TokPortal bundles from CSV files. Download templates, upload spreadsheets, and auto-configure videos.

CSV Import

Bulk-import videos into a bundle from a CSV file. The system parses each row, downloads video files from the provided URLs, uploads them to TokPortal storage, and configures the corresponding video positions automatically.

Download Templates

  • TikTok CSV Template — Columns: position, name, video_type, video_url, carousel_images, description, target_publish_start_date, editing_instructions, tiktok_sound_url
  • Instagram CSV Template — Columns: same as TikTok + instagram_content_type, instagram_location, instagram_collaborators, instagram_audio_name, instagram_add_to_story

Import Videos from CSV

POST /bundles/:id/videos/import-csv

Content-Type: multipart/form-data

Upload a .csv file as the file field. Each row maps to a video position in the bundle.

curl -X POST https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/import-csv \
  -H "X-API-Key: sk_xxx" \
  -F "file=@videos.csv"

Optional form fields

FieldDescription
auto_publish"true" or "1" to attempt POST /bundles/:id/publish after the import succeeds. The response will include an auto_publish object — see auto_publish behaviour. Failures here never fail the import.
curl -X POST https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/import-csv \
  -H "X-API-Key: sk_xxx" \
  -F "file=@videos.csv" \
  -F "auto_publish=true"

Response:

{
  "data": {
    "imported": 5,
    "skipped": 0,
    "errors": []
  }
}

WARNING: Partial success — never infer success from the status code POST /bundles/:id/videos/import-csv returns 200/201 even when some or all rows are rejected. Read imported and errors[]: rows that break the 3-per-day cap or the minimum lead time fail individually while the rest are imported. When auto_publish=true was sent, also read auto_publish.adjusted_videos — a row whose date had gone stale is moved forward at publish time rather than rejected.

Over MCP the call is marked isError only when zero rows succeeded while errors are present. Partial success and full success are reported as success, exactly as before, and the REST status code is unchanged.

If some rows fail validation, they are returned in the errors array while valid rows are still imported:

{
  "data": {
    "imported": 3,
    "skipped": 2,
    "errors": [
      {
        "row": 4,
        "message": "Date is too early. Videos must be scheduled at least 3 day(s) in advance (new accounts require setup and warming)."
      },
      {
        "row": 5,
        "message": "carousel_images is required when video_type is carousel."
      }
    ]
  }
}

CSV Column Reference

Each imported row configures a video slot the same way PUT /bundles/:id/videos/:position does, so it obeys the same required-field rules. A column TokPortal does not recognise does not fail the import: the header name comes back in meta.ignored_fields on the response — read it rather than assuming the column was applied. The one exception is target_publish_end_date, which is refused outright (see below). See Unknown and Inapplicable Fields.

ColumnRequiredDescription
positionYesVideo position in the bundle (1-indexed).
video_typeYesvideo or carousel. For Instagram: carousel + reel = Fixed Photos (video from images); carousel + post = swipeable Carousel.
descriptionYesCaption or description for the post.
target_publish_start_dateYesNote the column name — the JSON endpoints call this same value target_publish_date; the CSV header is target_publish_start_date, and an import without it fails with CSV_PARSE_ERROR. It is the first day of a 2-day publishing window in YYYY-MM-DD format (full date strings also accepted); the end day is derived as this day + 1 and cannot be set. The earliest allowed date is today + 3 days while the account is still being created, and today + 1 day once the account has been delivered or when the bundle runs on an existing account. Max 3 videos per day per bundle, counting videos already scheduled in the bundle — a 4th row on the same day fails with VIDEOS_PER_DAY_EXCEEDED.
target_publish_end_dateNot accepted. The window's end is derived (start + 1 day). A CSV carrying this column is refused with CSV_PARSE_ERROR rather than having it dropped, so an import cannot quietly produce a window you did not choose.
video_urlConditionalURL to the video file. Required when video_type is video. Google Drive links are supported.
carousel_imagesConditionalSemicolon-separated image URLs. Required when video_type is carousel.
tiktok_sound_urlConditionalTikTok sound URL. Required for TikTok carousels, optional for videos. TikTok bundles only — on any other platform the column is reported in ignored_fields.
volume_original_soundNoVolume of the original video sound, integer 0200 (percent). 100 = unchanged. Setting this for the first time on a video costs 1 credit.
volume_added_soundNoVolume of the added sound/music, integer 0200 (percent). 100 = unchanged. Setting this for the first time on a video costs 1 credit.
editing_instructionsNoFree-text instructions for the creator.
external_refNoYour own reference ID for this video.
ai_content_disclaimerNotrue or false. Free. Asks the manager to enable the platform's AI-generated content label.
disclose_as_adsNotrue or false. Free. Asks the manager to enable branded-content / paid-partnership disclosure.
instant_repost_as_storyNotrue or false. Paid, +1 credit on first enable. TikTok/Instagram video only — the manager reposts the video as a story with screenshot proof. Leave the column blank to keep the current value.
instagram_content_typeConditionalreel or post. Required on every Instagram slot. Instagram bundles only — on any other platform the column is reported in ignored_fields.
instagram_locationNoLocation tag for Instagram posts.
instagram_collaboratorsNoSemicolon-separated Instagram usernames.
instagram_audio_nameNoAudio track name for Instagram Reels.
instagram_add_to_storyNotrue or false.
youtube_titleNoReserved.
youtube_tagsNoReserved.
youtube_categoryNoReserved.
youtube_visibilityNoReserved.

The four youtube_* columns are reserved and have no effect on an imported row. YouTube accounts cannot be ordered through the API — bundle creation supports TikTok and Instagram only, and a YouTube bundle is rejected with YOUTUBE_DELAYED even though GET /credit-costs lists a YouTube price.

Video Download Behavior

The system downloads video and image files from the URLs provided in the CSV and re-uploads them to TokPortal's own storage. This means:

  • Google Drive links are supported (public or shared links).
  • Direct download URLs from any publicly accessible host work.
  • After import, the video_url and carousel_images fields on the video will point to TokPortal storage URLs, not the originals.

Formatting Rules

  • Dates — Use YYYY-MM-DD (e.g., 2026-03-15) or a full ISO date string (e.g., 2026-03-15T00:00:00Z).
  • Multi-value fields — Use semicolons (;) to separate multiple values in carousel_images, instagram_collaborators, and youtube_tags.
  • Boolean fields — Use true or false (case-insensitive).

Sample CSV — TikTok

position,video_type,description,target_publish_date,video_url,tiktok_sound_url,editing_instructions,external_ref
1,video,"Unboxing the new collection #ad",2026-03-15,https://drive.google.com/uc?id=abc123,https://www.tiktok.com/music/trending-789,"Add brand logo at the end",campaign-42-v1
2,video,"Trying the product for a week #sponsored",2026-03-18,https://drive.google.com/uc?id=def456,,,campaign-42-v2
3,carousel,"Top 5 looks from the drop",2026-03-21,,https://www.tiktok.com/music/chill-456,,campaign-42-v3

For row 3 (carousel), you would also need a carousel_images column:

position,video_type,description,target_publish_date,carousel_images,tiktok_sound_url,external_ref
3,carousel,"Top 5 looks from the drop",2026-03-21,"https://example.com/img1.jpg;https://example.com/img2.jpg;https://example.com/img3.jpg",https://www.tiktok.com/music/chill-456,campaign-42-v3

Sample CSV — Instagram

position,video_type,instagram_content_type,description,target_publish_date,video_url,instagram_location,instagram_collaborators,instagram_add_to_story,external_ref
1,video,reel,"Morning routine with our skincare ✨",2026-03-20,https://drive.google.com/uc?id=ghi789,"Los Angeles, California",brandofficial,true,ig-reel-001
2,carousel,post,"Summer collection — which is your fave?",2026-03-22,,"Miami, Florida",,false,ig-post-002