Bundles

Bundles – Campaign Management API

List, filter, and retrieve bundle details via the TokPortal API. Bundles group accounts and video slots for campaign management.

Bundles

A bundle groups an account configuration and optional video configuration for a specific country and platform. Bundles are the core unit of work in the TokPortal API.

Authentication

All endpoints require an API key passed via the X-API-Key header.

X-API-Key: sk_xxx

Base URL

https://app.tokportal.com/api/ext

Bundle Types

TypeDescription
account_onlyAccount creation and setup only. No videos.
account_and_videosAccount creation with video uploads.
videos_onlyVideo uploads on an existing account.

Lifecycle

Bundles follow a strict lifecycle:

pending_setup → published → accepted → completed

For bundles that include videos, the video phase adds intermediate states:

accepted → in_review → finalized → completed
StatusDescription
pending_setupBundle created, awaiting configuration.
publishedConfiguration complete, submitted for processing.
acceptedAccount work has been accepted by an operator.
in_reviewVideos are being reviewed.
finalizedVideos have been finalized.
completedAll work is done.
cancelledTerminal. The bundle will never accept publishes again — most commonly because the account was banned, or after an accepted ban-check with the cancel & refund resolution. cancelled_at and cancelled_reason say when and why; a bundle.cancelled webhook fires when it happens. Publish calls return 409 BUNDLE_INVALID_STATUS with current_status: "cancelled" plus the same cancelled_at / cancelled_reason details.

INFO: Automatic finalization after review Account listings and video listings that remain in in_review without user approval or a correction request are automatically moved to finalized after 72 hours. The window starts when the individual listing enters in_review, not when the bundle was created or published.

Platforms

PlatformValue
TikToktiktok
Instagraminstagram

Options

OptionDescriptionRestrictions
wants_advanced_warmingEnable advanced warming (recommended): search-term based warming with recorded proof and a data report. 5 credits per term after the workspace transition.TikTok/Instagram only. Requires advanced_warming_terms or advanced_warming_terms_count (3-30, multiples of 3). Cannot combine with wants_niche_warming.
advanced_warming_termsThe search terms for advanced warming.3-30 terms (count must be a multiple of 3), each 2-50 characters.
advanced_warming_terms_countBuy a target count now and configure the targets later.3-30, multiple of 3. Configure once with PUT /bundles/:id/warming-terms; unconfigured purchases are refunded after 14 days.
wants_niche_warmingEnable niche warming (review-based; will be deprecated in favor of advanced warming).Cannot combine with wants_advanced_warming; requires niche_warming_instructions.
wants_deep_warming⚠️ Discontinued — rejected. New orders fail with DEEP_WARMING_DEPRECATED. Use advanced warming instead.
wants_moderation⚠️ Deprecated — ignored. Replaced by the Comments API.
edits_quantityAdd edit slots.Cannot exceed videos_quantity.
auto_finalize_videosAutomatically finalize videos once ready.

Endpoints

INFO: TokPortal Coverage belongs to the account The Coverage boundary is the authoritative timestamp recorded by the production migration. Every TikTok or Instagram saved account that already exists at that cutover remains permanently grandfathered. A saved account created after it receives one TokPortal Coverage record when its account listing reaches in_review, unless its creation response marks it contractually exempt. Coverage is keyed by saved_account_id, not bundle_id. Bulk creation, single creation, dashboard creation, API creation and either MCP transport all converge on the same rule. Additional videos_only bundles on the same account never add another record or another 25-credit charge. The first 30 days are included after review submission, and the first 25-credit debit on day 30 covers the next 30 days. Bundle completion or cancellation never changes Coverage.

Bundles with no newly scheduled or posted video for 60 days are archived from the manager's active workspace, with inactive_archived_at and inactive_archive_reason returned by the API. This is not a cancellation, does not refund anything, and does not change TokPortal Coverage. New work reopens the bundle automatically.

MethodPathDescription
POST/bundlesCreate a bundle
GET/bundlesList all bundles
GET/bundles/:idGet a single bundle
PATCH/bundles/:idUpdate bundle settings
GET/bundles/:id/publish-readinessCheck publish-readiness
POST/bundles/:id/publishPublish a bundle
POST/bundles/:id/unpublishUnpublish a bundle
PUT/bundles/:id/warming-termsConfigure a deferred Advanced Warming target list

Computed State Fields

Both GET /bundles and GET /bundles/:id return a set of read-only computed fields to make integration logic simpler. These derive from the underlying bundle / account / video state — you do not need to recompute them client-side.

FieldTypeDescription
is_publishedbooleantrue once the bundle has been published (status is published, published_priority, accepted, or completed).
account_configuredbooleantrue when the bundle's account is in configured or finalized status (i.e., publish-eligible).
videos_totalintegerNumber of video slots on the bundle.
videos_configured_countintegerNumber of video slots whose status indicates they have been configured (any of configured, in_review, pending_corrections, accepted, published, finalized).
next_actionstring | nullSuggested next step in the configure → publish flow. One of configure_account, configure_videos, publish_bundle, or null if no action is required.

next_action is the recommended primary signal for an integration:

  • configure_account — call PUT /bundles/:id/account to set the account fields.
  • configure_videos — call PUT /bundles/:id/videos/batch (or per-position) to fill the video slots.
  • publish_bundle — call POST /bundles/:id/publish (or pass auto_publish: true on the next video configuration call).
  • null — the bundle is already past setup; nothing to do.

Remake info

Both GET /bundles and GET /bundles/:id return a remade object. When an account is banned or lost, TokPortal remakes it: the bundle is rebuilt in place under the same bundle_id and only the account handle changes. Because the bundle_id and your external_ref are stable across remakes, they are the recommended anchors for tracking an account that may be replaced over time.

FieldTypeDescription
was_remadebooleantrue if this bundle was remade at least once, otherwise false.
remade_countintegerNumber of times the bundle has been remade (0 when never remade).
old_usernamestring | nullThe previous username, captured at the time of the most recent remake. null if unknown or never remade.
remade_atstring (ISO 8601) | nullTimestamp of the most recent remake. null if never remade.

TIP: tip Subscribe to the account.remade webhook to be notified of remakes in real time instead of polling.

Cancellation info

Both GET /bundles and GET /bundles/:id return cancelled_at and cancelled_reason (both null unless the bundle is cancelled). When a validated ban cancels the bundle, cancelled_reason starts with Account banned: and the account.banned + bundle.cancelled webhooks fire; the banned account itself stays readable on GET /accounts/{id} with its ban details.


List Bundles

Retrieve all bundles for your organization.

GET /bundles

Query Parameters

ParamTypeDescription
statusstringFilter by bundle status: pending_setup, published, accepted, completed, etc.
bundle_typestringFilter by type: account_only, account_and_videos, videos_only.
account_statusstringFilter by account listing status: pending, configured, in_review, finalized. Useful for finding bundles with accounts ready to finalize.
platformstringFilter by platform: tiktok, instagram, youtube.
external_refstringFilter by your external reference.
pageintegerPage number (default: 1).
per_pageintegerResults per page (default: 25, max: 100).

Example

# List all bundles with accounts awaiting finalization
curl -X GET "https://app.tokportal.com/api/ext/bundles?account_status=in_review" \
  -H "X-API-Key: sk_xxx"

Response

{
  "data": [
    {
      "id": "bnd_abc123",
      "bundle_type": "account_and_videos",
      "platform": "tiktok",
      "country": "US",
      "status": "accepted",
      "account_status": "in_review",
      "videos_quantity": 5,
      "edits_quantity": 0,
      "used_edits": 0,
      "credit_cost": 60,
      "external_ref": "campaign-42",
      "existing_account_id": null,
      "auto_finalize_videos": true,
      "wants_niche_warming": true,
      "wants_deep_warming": false,
      "wants_advanced_warming": false,
      "advanced_warming_terms": [],
      "wants_moderation": false,
      "is_published": true,
      "account_configured": true,
      "videos_total": 5,
      "videos_configured_count": 5,
      "next_action": null,
      "remade": {
        "was_remade": false,
        "remade_count": 0,
        "old_username": null,
        "remade_at": null
      },
      "created_at": "2026-01-15T10:30:00Z",
      "updated_at": "2026-01-15T12:00:00Z"
    }
  ]
}

Get Bundle

Retrieve a single bundle by ID.

GET /bundles/:id

Example

curl -X GET https://app.tokportal.com/api/ext/bundles/bnd_abc123 \
  -H "X-API-Key: sk_xxx"

Response

{
  "data": {
    "id": "bnd_abc123",
    "bundle_type": "account_and_videos",
    "platform": "tiktok",
    "country": "US",
    "status": "published",
    "videos_quantity": 3,
    "edits_quantity": 0,
    "used_edits": 0,
    "credit_cost": 40,
    "external_ref": "campaign-42",
    "existing_account_id": null,
    "auto_finalize_videos": false,
    "wants_niche_warming": true,
    "wants_deep_warming": false,
    "wants_advanced_warming": false,
    "advanced_warming_terms": [],
    "wants_moderation": false,
    "is_published": true,
    "account_configured": true,
    "videos_total": 3,
    "videos_configured_count": 3,
    "next_action": null,
    "remade": {
      "was_remade": true,
      "remade_count": 1,
      "old_username": "cooluser123",
      "remade_at": "2026-02-02T09:15:00Z"
    },
    "saved_account_id": "7c9e0f1a-2b3c-4d5e-8f6a-1b2c3d4e5f60",
    "account": {
      "id": "al_abc123",
      "saved_account_id": "7c9e0f1a-2b3c-4d5e-8f6a-1b2c3d4e5f60",
      "username": "cooluser123",
      "visible_name": "Cool User",
      "biography": "Just vibes.",
      "profile_picture_url": "https://example.com/pic.jpg"
    },
    "videos": [
      {
        "id": "vid_xyz",
        "position": 1,
        "status": "pending",
        "name": "Video 1"
      }
    ],
    "created_at": "2026-01-15T10:30:00Z",
    "updated_at": "2026-01-15T12:00:00Z"
  }
}

NOTE: saved_account_id vs account.id GET /bundles/:id returns saved_account_id (top-level and inside account) — the id of the real created account, i.e. what GET /accounts/{id} uses. It is null until the account exists, and changes after a remake. The account.id field is the account listing id (the bundle's account slot), which is stable across remakes. Use saved_account_id whenever you need to act on the actual account (reveal credentials, fetch the latest handle, etc.). This is the clean way to obtain the new account id after a remake — poll this endpoint once you see account.finalized, or read it straight from the account.in_review / account.finalized webhooks.


Update Bundle

Update settings on an existing bundle. You can modify auto_finalize_videos, external_ref, and title at any time, regardless of bundle status.

PATCH /bundles/:id

Request Body

All fields are optional. At least one must be provided.

FieldTypeDescription
auto_finalize_videosbooleanToggle auto-approval of videos.
external_refstring | nullYour own reference ID. Max 200 characters. Must be unique per user. Set to null to clear.
titlestring | nullBundle title. Max 200 characters. Set to null to clear.

Example

curl -X PATCH https://app.tokportal.com/api/ext/bundles/bnd_abc123 \
  -H "X-API-Key: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "auto_finalize_videos": false,
    "external_ref": "campaign-42-updated"
  }'

Response

{
  "data": {
    "id": "bnd_abc123",
    "title": "My Campaign",
    "auto_finalize_videos": false,
    "external_ref": "campaign-42-updated",
    "updated_at": "2026-02-10T14:30:00Z"
  }
}

Error Responses

StatusCodeDescription
400VALIDATION_ERRORNo fields provided, or invalid values.
404BUNDLE_NOT_FOUNDBundle does not exist.
403BUNDLE_NOT_OWNEDBundle belongs to another user.
409DUPLICATE_EXTERNAL_REFAnother bundle already uses this external_ref.

Check Publish-Readiness

Returns whether a bundle is currently ready for POST /bundles/:id/publish, and if not, the list of blockers that publish would raise. Read-only — no side effects, no credit charge. Useful to surface "what's missing" in your UI before attempting publish.

GET /bundles/:id/publish-readiness

Example

curl -X GET https://app.tokportal.com/api/ext/bundles/bnd_abc123/publish-readiness \
  -H "X-API-Key: sk_xxx"

Response — ready

{
  "data": {
    "bundle_id": "bnd_abc123",
    "ready": true,
    "blockers": []
  }
}

Response — blockers present

{
  "data": {
    "bundle_id": "bnd_abc123",
    "ready": false,
    "blockers": [
      {
        "code": "ACCOUNT_MISSING_FIELDS",
        "message": "Account is missing required fields.",
        "details": { "missing_fields": ["profile_picture_url"] }
      }
    ]
  }
}

Blocker Codes

CodeMeaning
BUNDLE_ALREADY_PUBLISHEDThe bundle is already published or accepted.
BUNDLE_NOT_IN_SETUPThe bundle's status is not pending_setup (e.g., draft, cancelled).
ACCOUNT_MISSINGNo account configuration exists yet for the bundle.
ACCOUNT_NOT_CONFIGUREDThe account exists but its status is not yet configured or finalized.
ACCOUNT_MISSING_FIELDSThe account is missing one or more required fields (username, visible_name, profile_picture_url).