Source: https://developers.tokportal.com/create-bulk/
Markdown: https://developers.tokportal.com/create-bulk.md

# Create Bulk

Create multiple bundles in one request.

```http
POST /bundles/bulk
```

The bulk endpoint creates `accounts_count x platforms.length` bundles for one country. Each bundle produces exactly **one account**, so the batch size is the account count. The successful POST is the paid creation checkout: it creates every bundle and debits credits atomically for the whole batch. Publication happens later and never repeats the setup charge. The complete batch is checked against the client workspace's rolling capacity first; Team activity is included. A batch over capacity is rejected in full before any debit.

If `GET /credit-costs` returns `contract_bundle_allowance`, qualifying slots are allocated in deterministic bundle-ID order. A batch that crosses the remaining limit receives the contract price only for the qualifying bundles still covered. Any failure rolls back the whole batch, including all debits and allowance consumption.

Each eligible account delivered from the batch receives its own single TokPortal Coverage record. The production migration records the account cutoff. Every TikTok or Instagram saved account that already exists at that instant remains permanently grandfathered; a saved account created after it is Coverage-eligible unless its creation response marks it contractually exempt. Coverage belongs to the resulting saved account and is not multiplied by later bundles created for that account.

## Request Body

All fields are top-level. There is no `options` wrapper and no `metadata` field in the public contract.

| Field                          | Type      | Required | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ------------------------------ | --------- | -------: | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `platforms`                    | string\[] |      Yes | -       | One or more platforms. Public values: `tiktok`, `instagram` — those are the only orderable platforms. `accounts_count` accounts are created on **each** listed platform. `youtube` is rejected with `YOUTUBE_DELAYED` even though `GET /credit-costs` lists a `youtube_account_creation` price.                                                                                                                                                                                                                                                                                                                                                       |
| `country`                      | string    |      Yes | -       | TokPortal country code or supported alias. Use `GET /countries` to list countries available for new account creation. Disabled countries and countries restricted to existing-account `videos_only` work are excluded.                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `accounts_count`               | integer   |      Yes | -       | Number of accounts to create per platform. Min `1`, max `100`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `upload_accounts_count`        | integer   |       No | `0`     | How many accounts per platform should receive video slots. Must be `<= accounts_count`. The rest are `account_only` bundles.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `videos_per_account`           | integer   |       No | `0`     | Number of video slots for each account that receives videos. Max `500`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `wants_advanced_warming`       | boolean   |       No | `false` | Enable **Advanced Niche Warming** on every account of the batch. The standard rate is 5 credits per niche target per account — per target, never per day — with a 15-credit minimum per account. A workspace-specific `contract_bundle_allowance` can return another effective rate; `GET /credit-costs` is the authority. TikTok and Instagram only. Requires `advanced_warming_terms_count` **or** `advanced_warming_terms`, and is **mandatory whenever either of them is sent** — it is never inferred, and a batch that omits it is rejected in full. See the [Advanced Niche Warming guide](https://developers.tokportal.com/advanced-warming). |
| `advanced_warming_terms_count` | integer   |       No | -       | Number of niche targets purchased **per account** (3-30, multiples of 3). Requires `wants_advanced_warming: true` in the same body. Recommended flow: buy the count now, then write each bundle's targets via `PUT /bundles/{id}/warming-terms`. Do not send it alongside `advanced_warming_terms` unless it equals that list's length. Unconfigured purchases are auto-refunded after 14 days.                                                                                                                                                                                                                                                       |
| `advanced_warming_terms`       | string\[] |       No | -       | Alternative: the same explicit targets applied to every account of the batch (3-30 terms, count multiple of 3, each 2-50 characters, all distinct). Requires `wants_advanced_warming: true` in the same body.                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `wants_niche_warming`          | boolean   |       No | `false` | **⚠️ Deprecated.** Legacy — kept only for accounts being re-created after a refund; use Advanced Niche Warming (`wants_advanced_warming`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `wants_deep_warming`           | boolean   |       No | `false` | **⚠️ Deprecated.** Legacy — kept only for accounts being re-created after a refund; use Advanced Niche Warming (`wants_advanced_warming`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `wants_moderation`             | boolean   |       No | `false` | **⚠️ Deprecated — ignored.** Was the community-management add-on; replaced by the [Comments API](https://developers.tokportal.com/comments-overview).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `niche_warming_instructions`   | string    |       No | -       | **⚠️ Deprecated.** Legacy — kept only for accounts being re-created after a refund; use Advanced Niche Warming (`wants_advanced_warming`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `moderation_notice`            | string    |       No | -       | **⚠️ Deprecated — ignored.** Use the [Comments API](https://developers.tokportal.com/comments-overview).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `auto_finalize_videos`         | boolean   |       No | `true`  | Delivered videos are approved automatically and count as final. Set it to `false` to review each delivery yourself — approve it or request corrections — before it is finalized. Either way, this does **not** disable the automatic finalization that happens about **72 hours** after a video enters `in_review`. Changeable at any time via [Update Bundle](https://developers.tokportal.com/bundles#update-bundle).                                                                                                                                                                                                                               |
| `external_ref`                 | string    |       No | -       | Optional batch-level correlation reference. Max 200 characters. The same value is copied to every resulting bundle, so it is not a unique per-bundle ID and is not a retry key.                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |

## How It Works

Example:

```json
{
  "accounts_count": 10,
  "upload_accounts_count": 3,
  "platforms": ["tiktok", "instagram"],
  "country": "US",
  "videos_per_account": 5
}
```

Result:

| Platform  | `account_and_videos` | `account_only` | Total bundles |
| --------- | -------------------: | -------------: | ------------: |
| TikTok    |                    3 |              7 |            10 |
| Instagram |                    3 |              7 |            10 |
| **Total** |                **6** |         **14** |        **20** |

Total video slots: `3 upload accounts x 2 platforms x 5 videos = 30`.

Use each returned bundle `id` as the canonical per-bundle identifier. If you provide `external_ref`, treat it as a shared label for the whole batch. For exact retry/replay of the bulk checkout, send one stable `Idempotency-Key` for that logical request; never derive retry safety from `external_ref`.

## Example

```bash
curl -X POST https://app.tokportal.com/api/ext/bundles/bulk \
  -H "X-API-Key: sk_xxx" \
  -H "Idempotency-Key: bulk-q2-launch-us-v1" \
  -H "Content-Type: application/json" \
  -d '{
    "accounts_count": 5,
    "upload_accounts_count": 2,
    "platforms": ["tiktok", "instagram"],
    "country": "US",
    "videos_per_account": 3,
    "wants_advanced_warming": true,
    "advanced_warming_terms_count": 3,
    "external_ref": "q2-launch-us"
  }'
```

> `wants_advanced_warming: true` must travel with the count. Sending `advanced_warming_terms_count` alone rejects the **whole batch** with `ADVANCED_WARMING_FLAG_REQUIRED`, and nothing is charged.

## Response

This example uses the standard current tariff. Four bundles include three video slots and six are account-only; every bundle also carries the 3 Advanced Niche Warming targets requested above, at 5 credits per target per account.

```json
{
  "data": {
    "bundles_created": 10,
    "bundles": [
      {
        "id": "bnd_001",
        "platform": "tiktok",
        "bundle_type": "account_and_videos"
      },
      {
        "id": "bnd_002",
        "platform": "tiktok",
        "bundle_type": "account_only"
      }
    ],
    "review_mode": "auto",
    "review_mode_notice": "auto_finalize_videos defaulted to true: delivered videos are approved automatically and count as final. Send auto_finalize_videos: false (or PATCH each bundle) to review videos and request corrections before they are finalized.",
    "summary": {
      "platforms": ["tiktok", "instagram"],
      "accounts_per_platform": 5,
      "accounts_with_videos": 2,
      "accounts_without_videos": 3,
      "videos_per_account": 3
    }
  },
  "credits_charged": 494,
  "credits_remaining": 9506,
  "cost_breakdown": {
    "per_account_with_videos": {
      "account_creation": 32,
      "video_slots": 6,
      "edit_slots": 0,
      "advanced_warming": 15,
      "moderation": 0,
      "total": 53
    },
    "per_account_only": {
      "account_creation": 32,
      "video_slots": 0,
      "edit_slots": 0,
      "advanced_warming": 15,
      "moderation": 0,
      "total": 47
    },
    "accounts_with_videos": 2,
    "accounts_without_videos": 3,
    "platforms_count": 2,
    "total": 494
  }
}
```

The sample is shortened. The real response includes every created bundle and the server-calculated credit breakdown.

`review_mode` says how delivered videos are settled for every bundle in the batch: `auto` (the default) approves each delivery on arrival, `manual` holds it for your review. `review_mode_notice` appears **only when you omitted `auto_finalize_videos`** — it states that the default was applied and how to opt out, and it disappears as soon as you send the field explicitly.

## Validation Rules

- `upload_accounts_count` cannot exceed `accounts_count`.
- If `videos_per_account` is greater than `0`, set `upload_accounts_count` greater than `0` to create upload-capable bundles.
- `wants_advanced_warming: true` is **mandatory** whenever `advanced_warming_terms` or `advanced_warming_terms_count` is present. TokPortal never infers it: targets sent without the flag (absent **or** explicitly `false`) reject the **entire batch** with `ADVANCED_WARMING_FLAG_REQUIRED`, and nothing is charged.
- `wants_advanced_warming` requires **either** `advanced_warming_terms_count` **or** `advanced_warming_terms` (3-30 targets, multiples of 3), otherwise `ADVANCED_WARMING_TERMS`. Every platform of the batch must be TikTok or Instagram (`ADVANCED_WARMING_PLATFORM`). The standard minimum warming charge is 15 credits per account; use the active `contract_bundle_allowance` when present.
- Every term in `advanced_warming_terms` must survive cleanup intact. Entries are trimmed and compared case-insensitively; anything dropped as a duplicate or for being outside 2-50 characters fails the batch with `ADVANCED_WARMING_TERMS_REJECTED` and `details.dropped_terms`, rather than delivering and billing fewer targets per account than requested.
- If you send both `advanced_warming_terms` and `advanced_warming_terms_count`, they must agree. A disagreement fails with `ADVANCED_WARMING_COUNT_MISMATCH`; neither quantity silently wins.
- `platforms` must contain only `tiktok` and/or `instagram`. `youtube` is rejected with `YOUTUBE_DELAYED`.
- `country` must be enabled for your organization.
- An unrecognised field in the body — including inside a batch item — is rejected with `UNKNOWN_FIELD`. This is a full-replace write, so a misspelled name is refused rather than dropped; see [the note on the account configuration page](https://developers.tokportal.com/account-configuration#unrecognised-fields-are-refused-not-dropped).

> **NOTE: Legacy warming flags**
> `wants_niche_warming`, `wants_deep_warming` and `niche_warming_instructions` are kept only for accounts being re-created after a refund. Their behaviour is unchanged: `wants_niche_warming` and `wants_advanced_warming` remain mutually exclusive (`WARMING_CONFLICT`), `niche_warming_instructions` is still required when `wants_niche_warming` is `true`, and `wants_deep_warming: true` on a new order is still rejected with `DEEP_WARMING_DEPRECATED`. New integrations should use Advanced Niche Warming.

## Countries

The endpoint accepts one country per request. To create bundles in several countries, loop over country codes and call `POST /bundles/bulk` once per country.

Use:

```bash
curl -X GET https://app.tokportal.com/api/ext/countries \
  -H "X-API-Key: sk_xxx"
```

Use only the response's `data` list for bulk creation. The separate `videos_only_countries` list applies exclusively to video orders on existing accounts and is not valid for this endpoint.

## Error Responses

| Status | Code                              | Description                                                                                                                                                                                                                                    |
| ------ | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `INVALID_BODY`                    | Invalid request body or unsupported field.                                                                                                                                                                                                     |
| `400`  | `UNKNOWN_FIELD`                   | The body carries a field TokPortal does not recognise, at the top level or inside a batch item. This write is a full replace, so an unrecognised name is refused instead of dropped. Read `details.unknown_fields` and `details.did_you_mean`. |
| `400`  | `INVALID_COUNTRY`                 | Country is not enabled.                                                                                                                                                                                                                        |
| `400`  | `YOUTUBE_DELAYED`                 | `platforms` contains `youtube`. Bulk creation is TikTok and Instagram only, even though `GET /credit-costs` lists a YouTube price.                                                                                                             |
| `400`  | `ADVANCED_WARMING_FLAG_REQUIRED`  | `advanced_warming_terms` and/or `advanced_warming_terms_count` was sent without `wants_advanced_warming: true`. The whole batch is rejected and nothing is charged.                                                                            |
| `400`  | `ADVANCED_WARMING_TERMS_REJECTED` | A term was removed by cleanup — a case-insensitive duplicate, or outside 2-50 characters. `details.dropped_terms` lists them. The batch is refused rather than delivering fewer targets than requested.                                        |
| `400`  | `ADVANCED_WARMING_COUNT_MISMATCH` | `advanced_warming_terms` and `advanced_warming_terms_count` state different quantities. Send one or the other.                                                                                                                                 |
| `400`  | `ADVANCED_WARMING_TERMS`          | Provide `advanced_warming_terms` or `advanced_warming_terms_count` (3-30, multiples of 3).                                                                                                                                                     |
| `400`  | `ADVANCED_WARMING_PLATFORM`       | Advanced Niche Warming is only available for TikTok and Instagram.                                                                                                                                                                             |
| `400`  | `WARMING_CONFLICT`                | Advanced Niche Warming cannot be combined with the legacy `wants_niche_warming` flag.                                                                                                                                                          |
| `400`  | `DEEP_WARMING_DEPRECATED`         | Legacy: `wants_deep_warming: true` on a new order. Use Advanced Niche Warming.                                                                                                                                                                 |
| `402`  | `INSUFFICIENT_CREDITS`            | Not enough credits for the full bulk operation.                                                                                                                                                                                                |
| `409`  | `BUNDLE_PRICING_CHANGED`          | The authoritative price or contract allowance changed before checkout. The full batch was rolled back with no debit or allowance consumption. Fetch `GET /credit-costs` again and retry with a new `Idempotency-Key`.                          |
| `429`  | `capacity_cooldown`               | The full batch would exceed the workspace's rolling bundle capacity. Retry later.                                                                                                                                                              |

## Next Steps

- [Configure account profiles](https://developers.tokportal.com/account-configuration) for created bundles.
- [Configure videos](https://developers.tokportal.com/configure-videos) for `account_and_videos` bundles.
- [Publish bundles](https://developers.tokportal.com/publish-unpublish) when they are ready.
