Source: https://developers.tokportal.com/csv-import/
Markdown: https://developers.tokportal.com/csv-import.md

# 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](https://developers.tokportal.com/templates/tiktok-template.csv)** — Columns: position, name, video\_type, video\_url, carousel\_images, description, target\_publish\_start\_date, editing\_instructions, tiktok\_sound\_url
- **[Instagram CSV Template](https://developers.tokportal.com/templates/instagram-template.csv)** — 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.

```bash
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](https://developers.tokportal.com/videos/configure-videos#auto_publish--configure-and-publish-in-one-call). Failures here never fail the import. |

```bash
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:**

```json
{
  "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:

```json
{
  "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](https://developers.tokportal.com/videos/configure-videos#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 carousels, Reels, or Stories (optional, max 200 characters).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `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_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

```csv
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:

```csv
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

To add background music to an Instagram carousel, include the optional `instagram_audio_name` column with the track name. Use `video_type=carousel` and `instagram_content_type=post` for a swipeable carousel. See the [Instagram carousel music example](https://developers.tokportal.com/configure-videos#instagram-carousel-background-music).

```csv
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
```
