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
| Field | Description |
|---|---|
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-csvreturns 200/201 even when some or all rows are rejected. Readimportedanderrors[]: rows that break the 3-per-day cap or the minimum lead time fail individually while the rest are imported. Whenauto_publish=truewas sent, also readauto_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
isErroronly 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.
| Column | Required | Description |
|---|---|---|
position | Yes | Video position in the bundle (1-indexed). |
video_type | Yes | video or carousel. For Instagram: carousel + reel = Fixed Photos (video from images); carousel + post = swipeable Carousel. |
description | Yes | Caption or description for the post. |
target_publish_start_date | Yes | Note 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_date | — | Not 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_url | Conditional | URL to the video file. Required when video_type is video. Google Drive links are supported. |
carousel_images | Conditional | Semicolon-separated image URLs. Required when video_type is carousel. |
tiktok_sound_url | Conditional | TikTok 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_sound | No | Volume of the original video sound, integer 0–200 (percent). 100 = unchanged. Setting this for the first time on a video costs 1 credit. |
volume_added_sound | No | Volume of the added sound/music, integer 0–200 (percent). 100 = unchanged. Setting this for the first time on a video costs 1 credit. |
editing_instructions | No | Free-text instructions for the creator. |
external_ref | No | Your own reference ID for this video. |
ai_content_disclaimer | No | true or false. Free. Asks the manager to enable the platform's AI-generated content label. |
disclose_as_ads | No | true or false. Free. Asks the manager to enable branded-content / paid-partnership disclosure. |
instant_repost_as_story | No | true 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_type | Conditional | reel or post. Required on every Instagram slot. Instagram bundles only — on any other platform the column is reported in ignored_fields. |
instagram_location | No | Location tag for Instagram posts. |
instagram_collaborators | No | Semicolon-separated Instagram usernames. |
instagram_audio_name | No | Audio track name for Instagram Reels. |
instagram_add_to_story | No | true or false. |
youtube_title | No | Reserved. |
youtube_tags | No | Reserved. |
youtube_category | No | Reserved. |
youtube_visibility | No | Reserved. |
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_urlandcarousel_imagesfields 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 incarousel_images,instagram_collaborators, andyoutube_tags. - Boolean fields — Use
trueorfalse(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