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.

Platforms

PlatformValue
TikToktiktok
Instagraminstagram

Options

OptionDescriptionRestrictions
wants_niche_warmingEnable niche warming.Cannot combine with wants_deep_warming; requires niche_warming_instructions.
wants_deep_warmingEnable deep warming.Instagram only. Cannot combine with wants_niche_warming.
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

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

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.


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_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_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).