Create Bulk - Multiple Bundles
Create multiple TokPortal bundles in one API request. Bulk creation supports one country, one or more platforms, optional video slots, and top-level Advanced Niche Warming options.
Create Bulk
Create multiple bundles in one request.
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. |
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. |
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. |
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. |
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:
{
"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 |
| 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
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: truemust travel with the count. Sendingadvanced_warming_terms_countalone rejects the whole batch withADVANCED_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.
{
"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_countcannot exceedaccounts_count.- If
videos_per_accountis greater than0, setupload_accounts_countgreater than0to create upload-capable bundles. wants_advanced_warming: trueis mandatory wheneveradvanced_warming_termsoradvanced_warming_terms_countis present. TokPortal never infers it: targets sent without the flag (absent or explicitlyfalse) reject the entire batch withADVANCED_WARMING_FLAG_REQUIRED, and nothing is charged.wants_advanced_warmingrequires eitheradvanced_warming_terms_countoradvanced_warming_terms(3-30 targets, multiples of 3), otherwiseADVANCED_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 activecontract_bundle_allowancewhen present.- Every term in
advanced_warming_termsmust 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 withADVANCED_WARMING_TERMS_REJECTEDanddetails.dropped_terms, rather than delivering and billing fewer targets per account than requested. - If you send both
advanced_warming_termsandadvanced_warming_terms_count, they must agree. A disagreement fails withADVANCED_WARMING_COUNT_MISMATCH; neither quantity silently wins. platformsmust contain onlytiktokand/orinstagram.youtubeis rejected withYOUTUBE_DELAYED.countrymust 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.
NOTE: Legacy warming flags
wants_niche_warming,wants_deep_warmingandniche_warming_instructionsare kept only for accounts being re-created after a refund. Their behaviour is unchanged:wants_niche_warmingandwants_advanced_warmingremain mutually exclusive (WARMING_CONFLICT),niche_warming_instructionsis still required whenwants_niche_warmingistrue, andwants_deep_warming: trueon a new order is still rejected withDEEP_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:
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 for created bundles.
- Configure videos for
account_and_videosbundles. - Publish bundles when they are ready.