API Reference

API Reference

Generated TokPortal OpenAPI reference with curl, Node, Python, and Go examples for every operation.

API Reference

This page is generated from the public OpenAPI schema. Do not edit it by hand.

Generated operations: 91.

Profile

Get authenticated user

GET /me

Operation ID: getCurrentUser

Returns the authenticated workspace: profile, role, analytics access tier and live credit balance. Call once at the start of a session to learn what this key can do.

StatusDescription
200Authenticated user profile and credit balance.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/me" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/me', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/me',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/me", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Update safe workspace settings

PATCH /me

Operation ID: updateCurrentUserSettings

Updates client-owned workspace profile fields used by Operator context. Does not expose auth, role, credit, staff, or manager settings.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (UpdateCurrentUserSettingsRequest)

StatusDescription
200Updated safe workspace settings.
400Invalid workspace setting.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X PATCH "https://app.tokportal.com/api/ext/me" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "company_name": "company_name",
  "website": "website",
  "company_niche": "company_niche",
  "organic_strategy": "organic_strategy"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/me', {
  method: 'PATCH',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "company_name": "company_name",
  "website": "website",
  "company_niche": "company_niche",
  "organic_strategy": "organic_strategy"
})
});
const data = await response.json();

Python



response = requests.request(
    'PATCH',
    'https://app.tokportal.com/api/ext/me',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"company_name\": \"company_name\",\n  \"website\": \"website\",\n  \"company_niche\": \"company_niche\",\n  \"organic_strategy\": \"organic_strategy\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("PATCH", "https://app.tokportal.com/api/ext/me", strings.NewReader("{\n  \"company_name\": \"company_name\",\n  \"website\": \"website\",\n  \"company_niche\": \"company_niche\",\n  \"organic_strategy\": \"organic_strategy\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Accounts

Get TokPortal Coverage status

GET /accounts/{id}/managed-subscription

Operation ID: getAccountManagedSubscription

Returns the effective TokPortal Coverage status, current 30-day period, account-specific rate, unpaid periods, task access, and an exact timestamped reactivation quote for one eligible saved account. At current_period_end, status and task_access change immediately even if recorded_status remains included or active for a few minutes until the renewal worker persists the transition; period_expired makes that boundary explicit. A zero-credit quote is valid. Use its current_period_end and lock_version snapshot for reactivation. An eligible TikTok or Instagram account created before the global Coverage cutoff is grandfathered and has no subscription record. A missing record can also mean the account is not Coverage-eligible or has not reached delivery, so clients must not infer grandfathering from a generic 404. Coverage is independent from bundle completion or closure.

ParameterInRequiredDescription
idpathYesSaved account ID.
StatusDescription
200TokPortal Coverage status.
401Missing, invalid, or revoked API key.
404Account or coverage record not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Reactivate TokPortal Coverage

POST /accounts/{id}/managed-subscription/reactivate

Operation ID: reactivateAccountManagedSubscription

Reactivates TokPortal Coverage from an explicit GET snapshot, and debits the projected unpaid 30-day periods — read the quote from getAccountManagedSubscription and confirm the amount with the user first. Reactivation is free while the current billing period is already paid or included; Coverage benefits and tasks remain paused until reactivation. At and after current_period_end, the endpoint accepts the effective lapsed state even if recorded_status has not yet been updated by the renewal worker, and charges exactly the projected unpaid periods without adding an extra period. The expected credits, period end, and lock version are checked atomically before any debit or task resume. Scheduled videos receive new future dates while preserving their cadence.

ParameterInRequiredDescription
idpathYesSaved account ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (ManagedAccountSubscriptionReactivationRequest)

StatusDescription
200Coverage reactivated and withheld work resumed.
401Missing, invalid, or revoked API key.
402Insufficient credits. The response details include the exact required amount.
404Account or coverage record not found.
409Coverage is not paused, the account is unavailable, or the quote changed. Fetch a fresh snapshot before retrying QUOTE_CHANGED.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription/reactivate" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Idempotency-Key: reactivateAccountManagedSubscription-example-1" \
  -H "Content-Type: application/json" \
  -d '{
  "expected_credits": 25,
  "expected_current_period_end": "2026-06-01T00:00:00Z",
  "expected_lock_version": 25
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription/reactivate', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'reactivateAccountManagedSubscription-example-1',
  },
  body: JSON.stringify({
  "expected_credits": 25,
  "expected_current_period_end": "2026-06-01T00:00:00Z",
  "expected_lock_version": 25
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription/reactivate',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"], "Idempotency-Key": "reactivateAccountManagedSubscription-example-1"},
    json=json.loads("{\n  \"expected_credits\": 25,\n  \"expected_current_period_end\": \"2026-06-01T00:00:00Z\",\n  \"expected_lock_version\": 25\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription/reactivate", strings.NewReader("{\n  \"expected_credits\": 25,\n  \"expected_current_period_end\": \"2026-06-01T00:00:00Z\",\n  \"expected_lock_version\": 25\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Idempotency-Key", "reactivateAccountManagedSubscription-example-1")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Pause TokPortal Coverage

POST /accounts/{id}/managed-subscription/cancel

Operation ID: cancelAccountManagedSubscription

Immediately blocks new and ongoing TokPortal work for the account. Payments are not refunded. If a paid or included current period remains, reactivation is free until its original end. Otherwise it costs exactly the unpaid 30-day periods at the rate returned by the Coverage status endpoint.

ParameterInRequiredDescription
idpathYesSaved account ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Coverage paused and current work withheld.
401Missing, invalid, or revoked API key.
404Account or coverage record not found.
409Coverage cannot be cancelled in its current state.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription/cancel" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Idempotency-Key: cancelAccountManagedSubscription-example-1"

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription/cancel', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Idempotency-Key': 'cancelAccountManagedSubscription-example-1',
  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription/cancel',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"], "Idempotency-Key": "cancelAccountManagedSubscription-example-1"}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/managed-subscription/cancel", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Idempotency-Key", "cancelAccountManagedSubscription-example-1")
resp, err := http.DefaultClient.Do(req)

List delivered accounts

GET /accounts

Operation ID: listAccounts

Lists delivered saved accounts (real accounts that exist on the platform). Note the product has two ids: the account listing id carried by a bundle is stable across remakes, while the saved_account_id returned here is replaced every time an account is remade.

ParameterInRequiredDescription
pagequeryNoPage number.
per_pagequeryNoItems per page.
platformqueryNoFilter by platform.
countryqueryNoFilter by country code or country alias.
bannedqueryNoFilter by ban state. true returns only banned accounts (staff-validated or park-scan detected, matching the banned response field); false returns only non-banned accounts.
StatusDescription
200Paginated delivered account list.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/accounts?page=example&per_page=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts?page=example&per_page=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/accounts?page=example&per_page=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/accounts?page=example&per_page=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

List ban reports and appeals

GET /account-bans

Operation ID: listAccountBans

Pollable list of validated ban reports for the caller's delivered accounts, covering the whole lifecycle: appeal_pending (platform appeal filed, account unavailable but NOT yet banned), appeal_accepted (account survived), appeal_refused / no_appeal_banned (confirmed ban), and the staff commercial resolution (refund / remake / no_remake with a machine-readable reason_code such as tos_ban). While a report is appeal_pending, do NOT order a replacement account: a duplicate can trigger a ban-evasion strike on the platform, which is why TokPortal itself waits for the appeal. Key your reports on bundle_id — account_id can be null after a staff reset. A resolution of refund returns setup, warming and unused video slots, but not work already delivered and not the Coverage period, and the restored credits expire 60 days after restoration (see the credits.restored event in listWebhookEvents). This is the REST counterpart of the account.banned, account.ban_appeal.submitted, account.ban_appeal.resolved and account.ban_resolution.decided webhook events. Only staff/CM-validated bans appear here — detections from TokPortal's internal health scan are never listed. Poll with the since parameter (updated_at watermark) to pick up new reports and status changes.

ParameterInRequiredDescription
pagequeryNoPage number.
per_pagequeryNoItems per page.
statusqueryNoFilter by appeal lifecycle status.
resolutionqueryNoFilter by staff commercial resolution. 'pending' selects confirmed bans still awaiting the staff decision.
account_idqueryNoFilter by saved account ID.
sincequeryNoOnly reports updated at or after this ISO 8601 timestamp. Use the highest updated_at you have seen as a polling watermark.
include_screenshotsqueryNoWhen true, each report includes a signed 7-day URL of the ban-evidence screenshot when one exists.
StatusDescription
200Paginated ban report list.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/account-bans?page=example&per_page=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/account-bans?page=example&per_page=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/account-bans?page=example&per_page=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/account-bans?page=example&per_page=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Get a delivered account

GET /accounts/{id}

Operation ID: getAccount

Returns one delivered account: handle, platform, Coverage state, ban state and warming summary. A saved_account_id invalidated by a remake returns 404 — re-read the bundle to get the new one.

ParameterInRequiredDescription
idpathYesSaved account ID.
StatusDescription
200Delivered account details. For an eligible TikTok or Instagram account created before the global Coverage cutoff, managed_subscription is null because the account is grandfathered. It can also be null for an ineligible platform or when no Coverage record exists. Otherwise, this stable technical TokPortal Coverage field contains the one per-saved-account status, period, unpaid periods, task access and reactivation price.
401Missing, invalid, or revoked API key.
404Account not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Update account commenting profile

PATCH /accounts/{id}/commenting-profile

Operation ID: updateAccountCommentingProfile

Sets the internal commenting-autopilot metadata (niche, persona, goal, language, tracked handles) that TokPortal uses to draft comments for this account. It NEVER changes the account's public profile — use createAccountEditRequest to change a delivered account's username, visible name, biography, picture or link. The account must belong to the API key owner and must have active TokPortal Coverage, unless it is permanently grandfathered. Revealed/detached, banned, paused, lapsed and unrecoverable accounts are read-only.

ParameterInRequiredDescription
idpathYesSaved account ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (UpdateAccountCommentingProfileRequest)

StatusDescription
200Updated account commenting profile.
400Invalid commenting profile body.
401Missing, invalid, or revoked API key.
404Account not found.
409Account mutation blocked. Inspect error.details.reason; reactivate TokPortal Coverage when the state is recoverable.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X PATCH "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/commenting-profile" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "niche": "niche",
  "persona": "persona",
  "goal": "promotion",
  "language": "language"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/commenting-profile', {
  method: 'PATCH',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "niche": "niche",
  "persona": "persona",
  "goal": "promotion",
  "language": "language"
})
});
const data = await response.json();

Python



response = requests.request(
    'PATCH',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/commenting-profile',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"niche\": \"niche\",\n  \"persona\": \"persona\",\n  \"goal\": \"promotion\",\n  \"language\": \"language\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("PATCH", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/commenting-profile", strings.NewReader("{\n  \"niche\": \"niche\",\n  \"persona\": \"persona\",\n  \"goal\": \"promotion\",\n  \"language\": \"language\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

List bundles for a delivered account

GET /accounts/{id}/bundles

Operation ID: listAccountBundles

Lists every bundle attached to one delivered account, including cancelled ones. Use it to see an account's full order history before ordering more work on it.

ParameterInRequiredDescription
idpathYesSaved account ID.
pagequeryNoPage number.
per_pagequeryNoItems per page.
statusqueryNoFilter by bundle status.
StatusDescription
200Paginated bundles for account.
401Missing, invalid, or revoked API key.
404Account not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/bundles?page=example&per_page=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/bundles?page=example&per_page=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/bundles?page=example&per_page=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/bundles?page=example&per_page=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Retrieve latest account verification code

POST /accounts/{id}/verification-code

Operation ID: retrieveAccountVerificationCode

Retrieving a verification code is the same irreversible first-access event as revealing credentials. Its policy is determined by this saved account's created_at timestamp against the immutable managed_pricing_new_customer_cutover_at value, never by the workspace action-pricing cohort or August 14 grace deadline. A saved account created before the cutoff permanently keeps the prior API contract: 0 credits, no new versioned acknowledgment body required, no TokPortal detachment, existing task access remains available, and support plus ban coverage end after access. A saved account created at or after the cutoff follows the managed policy: normally 150 credits, or the stored $10 legacy / $15 new per-30-day rate when Account Owning is already admin-approved. Managed access permanently detaches the account, makes it read-only, and ends TokPortal Coverage, task access, analytics updates, support, ban protection, replacement, refunds, compensation and credit restoration. For a new-policy account, missing explicit acceptance fails with 428 and returns the exact account-specific terms, price and policy version. A changed quote returns 409 before any debit or reveal. Reaching the Account Owning eligibility threshold only submits an admin review request; it never activates the agreement automatically. While approval is pending, reveal remains available for 150 credits on a post-cutoff account. Do not send Idempotency-Key: this response contains a verification secret and is never stored in the replay ledger. A request that includes the header is rejected before any ledger claim, reveal, debit, or inbox access with IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE (400). After an uncertain transport result, fetch the safe account state before deciding whether to call this endpoint again without the header.

ParameterInRequiredDescription
idpathYesSaved account ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (AcknowledgeSupportForfeitRequest)

StatusDescription
200Verification code result.
400Idempotency-Key is not supported because the response contains a verification secret. Remove the header and retry only after reconciling account state. Error code: IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE.
401Missing, invalid, or revoked API key.
402Insufficient credits for the applicable one-time reveal, or incomplete activation or billing for an admin-approved Account Owning Fee agreement.
404Account or verification code not found.
409CREDENTIAL_REVEAL_QUOTE_CHANGED: the submitted non-empty policy version or approved price snapshot is stale. Nothing was charged or revealed. Show the current disclosure and exact expected_credit_cost returned in error.details, obtain fresh consent, then retry with details.policy_version.
428CREDENTIALS_ACKNOWLEDGMENT_REQUIRED: acceptance or its policy version is missing. Nothing was charged or revealed. Show error.details to the account owner, obtain consent, then retry with acknowledge_support_forfeit=true and details.policy_version.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/verification-code" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "acknowledge_support_forfeit": true,
  "policy_version": "VERSION_RETURNED_BY_428"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/verification-code', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "acknowledge_support_forfeit": true,
  "policy_version": "VERSION_RETURNED_BY_428"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/verification-code',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"acknowledge_support_forfeit\": true,\n  \"policy_version\": \"VERSION_RETURNED_BY_428\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/verification-code", strings.NewReader("{\n  \"acknowledge_support_forfeit\": true,\n  \"policy_version\": \"VERSION_RETURNED_BY_428\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Reveal delivered account credentials

POST /accounts/{id}/reveal-credentials

Operation ID: revealAccountCredentials

The first credential or verification-code access is priced from this saved account's created_at timestamp against the immutable managed_pricing_new_customer_cutover_at value, never from the workspace action-pricing cohort or August 14 grace deadline. A saved account created before the cutoff permanently keeps the prior API contract: the reveal costs 0 credits, requires no new versioned acknowledgment body, and is irreversible; support and ban coverage end, but the account is not detached and existing TokPortal task access remains available. A saved account created at or after the cutoff follows the managed policy and requires the explicit versioned acknowledgment handshake: normally 150 credits, or the stored $10 legacy / $15 new per-30-day rate when Account Owning is already admin-approved. Crossing the Account Owning eligibility threshold only submits an admin review request and never activates it automatically. While approval is pending, reveal remains available for 150 credits on a post-cutoff account. Under the managed policy the charge is final and non-refundable, and reveal permanently detaches the account, makes it read-only, and ends TokPortal Coverage, all task access, analytics updates, support, ban protection, replacement, refunds, compensation and credit restoration. TokPortal is not responsible for later access, performance, reach, security, restrictions or bans. The debit or Account Owning activation, reveal marker and Coverage shutdown commit atomically. Previously revealed accounts are not charged a second time. Do not send Idempotency-Key: this response contains credentials and is never stored in the replay ledger. A request that includes the header is rejected before any ledger claim, reveal, debit, or secret access with IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE (400). After an uncertain transport result, fetch the safe account state before deciding whether to call this endpoint again without the header.

ParameterInRequiredDescription
idpathYesSaved account ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (AcknowledgeSupportForfeitRequest)

StatusDescription
200Credentials reveal result.
400Idempotency-Key is not supported because the response contains credentials. Remove the header and retry only after reconciling account state. Error code: IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE.
401Missing, invalid, or revoked API key.
402Insufficient credits for the applicable one-time reveal, or incomplete activation or billing for an admin-approved Account Owning Fee agreement.
404Account not found.
409CREDENTIAL_REVEAL_QUOTE_CHANGED: the submitted non-empty policy version or approved price snapshot is stale. Nothing was charged or revealed. Show the current disclosure and exact expected_credit_cost returned in error.details, obtain fresh consent, then retry with details.policy_version.
428CREDENTIALS_ACKNOWLEDGMENT_REQUIRED: acceptance or its policy version is missing. Nothing was charged or revealed. Show error.details to the account owner, obtain consent, then retry with acknowledge_support_forfeit=true and details.policy_version.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/reveal-credentials" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "acknowledge_support_forfeit": true,
  "policy_version": "VERSION_RETURNED_BY_428"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/reveal-credentials', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "acknowledge_support_forfeit": true,
  "policy_version": "VERSION_RETURNED_BY_428"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/reveal-credentials',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"acknowledge_support_forfeit\": true,\n  \"policy_version\": \"VERSION_RETURNED_BY_428\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/reveal-credentials", strings.NewReader("{\n  \"acknowledge_support_forfeit\": true,\n  \"policy_version\": \"VERSION_RETURNED_BY_428\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Get active account edit request

GET /accounts/{id}/edit-request

Operation ID: getAccountEditRequest

Returns the account's profile edit requests, newest first, with their status and credit cost. Only one request can be open per account at a time, so check here before calling createAccountEditRequest.

ParameterInRequiredDescription
idpathYesSaved account ID.
StatusDescription
200Active edit request.
401Missing, invalid, or revoked API key.
404Account not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/edit-request" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/edit-request', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/edit-request',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/edit-request", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Request profile edits for a delivered account

POST /accounts/{id}/edit-request

Operation ID: createAccountEditRequest

Requests profile changes on an account that is already delivered and debits 8 credits. This is the ONLY way to change a delivered account's public username, visible name, biography, picture or link-in-bio: addEditSlots is unrelated (it buys video-editing slots) and configureBundleAccount only works before the account is delivered. Besides the username and visible name, supply at least one of requested_biography, requested_profile_picture_url or requested_link_in_bio — a request that changes nothing else is rejected. Only one request can be open per account at a time (409 otherwise); check getAccountEditRequest first. Active TokPortal Coverage and a routable active account manager are required. TokPortal uses the account's current active manager, then its still-active manager relationship, then eligible non-cancelled order history. Completed bundles remain eligible without a delivery-age limit; cancelled orders are never restored. Assignment, debit, and task creation are atomic, so an unavailable manager never costs credits. The task appears directly in that manager's calendar.

ParameterInRequiredDescription
idpathYesSaved account ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (AccountEditRequest)

StatusDescription
201Edit request created.
400Invalid edit request.
401Missing, invalid, or revoked API key.
404Account not found.
409Coverage is inactive, no active account manager or non-cancelled support order is available, or an active edit request already exists. No credits are charged on these failures.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/edit-request" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "requested_username": "requested_username",
  "requested_visible_name": "requested_visible_name",
  "requested_biography": "requested_biography"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/edit-request', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "requested_username": "requested_username",
  "requested_visible_name": "requested_visible_name",
  "requested_biography": "requested_biography"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/edit-request',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"requested_username\": \"requested_username\",\n  \"requested_visible_name\": \"requested_visible_name\",\n  \"requested_biography\": \"requested_biography\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/edit-request", strings.NewReader("{\n  \"requested_username\": \"requested_username\",\n  \"requested_visible_name\": \"requested_visible_name\",\n  \"requested_biography\": \"requested_biography\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Reference

List available countries

GET /countries

Operation ID: listCountries

Lists the valid country values for bundle creation. Most are ISO-3166-1 alpha-2 (FR, DE, BR), but TokPortal's own codes are the authority and two of them are not alpha-2: the United States is USA and the United Kingdom is UK. Their ISO aliases US and GB are accepted and resolved to those two, so send a code from this list verbatim and never assume alpha-2. Call before createBundle or createBundlesBulk: country is validated against this list, and a full country name such as "United States" is rejected. videos_only_countries applies only to video orders on an existing account and is never valid for /bundles/bulk.

StatusDescription
200Country availability by order type. data contains countries available for new account creation; videos_only_countries contains countries available for video orders on an existing account. Fully disabled countries are excluded from both lists.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/countries"

Node

const response = await fetch('https://app.tokportal.com/api/ext/countries', {
  method: 'GET',
  headers: {


  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/countries',
    headers={}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/countries", nil)
resp, err := http.DefaultClient.Do(req)

List available platforms

GET /platforms

Operation ID: listPlatforms

Lists the orderable platforms and, per video type, the exact required and optional fields. Call before configuring any slot: TikTok carousels require tiktok_sound_url, Instagram posts and reels require instagram_content_type, and a story takes exactly one of video_url or story_image_url and no description. Only TikTok and Instagram can be ordered; YouTube fields exist on the video schema but YouTube bundles are rejected with YOUTUBE_DELAYED.

StatusDescription
200Supported social platforms.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/platforms"

Node

const response = await fetch('https://app.tokportal.com/api/ext/platforms', {
  method: 'GET',
  headers: {


  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/platforms',
    headers={}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/platforms", nil)
resp, err := http.DefaultClient.Do(req)

Credits

Get credit pricing

GET /credit-costs

Operation ID: getCreditCosts

Authoritative live credit prices and workspace cohort. Call it immediately before any purchase and never hard-code or recall a price. Not every key is orderable: youtube_account_creation is listed for reference only (YouTube bundles are rejected with YOUTUBE_DELAYED), and niche_warming and deep_warming are legacy prices published for backward compatibility only — they are not products a caller may choose, and advanced_warming (Advanced Niche Warming, priced per niche target) is the only warming TokPortal sells. managed_account_subscription (TokPortal Coverage) recurs every 30 days per account after the included first period, so a multi-account quote must include it as a monthly cost. There is no dry-run endpoint: assemble the total from this table (account setup + video slots + Advanced Niche Warming targets + edit slots + paid video options), apply the returned cohort and contract_bundle_allowance, and confirm it with the user before creating anything.

StatusDescription
200The authenticated workspace's effective action-price table, its cohort and transition state, both cohort dates, TokPortal Coverage price and account eligibility, the saved-account credential-reveal cutoff, Account Owning Fee approval threshold and legacy/new agreement rates, and the 1-credit Comments API task. credential_reveal is an informational fallback only: the exact first-access quote is account-specific and is returned by HTTP 428 or 409 from the credential or verification-code endpoint. The former per-bundle moderation add-on is unavailable. managed_account_subscription remains the stable technical cost key.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/credit-costs" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/credit-costs', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/credit-costs',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/credit-costs", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Get credit balance

GET /credits/balance

Operation ID: getCreditBalance

Returns the currently spendable credit balance and upcoming expirations. Check it before any purchase: a 402 on a mutation means this was short.

StatusDescription
200Current credit balance.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/credits/balance" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/credits/balance', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/credits/balance',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/credits/balance", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

List credit transactions

GET /credits/history

Operation ID: listCreditTransactions

Paginated ledger of every credit debit and refund, newest first. Use it to reconcile what a bundle or batch actually cost, since server-side pricing (workspace cohort, contract allowance) can differ from the list price.

ParameterInRequiredDescription
pagequeryNoPage number.
per_pagequeryNoItems per page.
date_fromqueryNoFilter transactions created on or after this date.
date_toqueryNoFilter transactions created before the day after this date.
StatusDescription
200Paginated credit transaction history.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/credits/history?page=example&per_page=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/credits/history?page=example&per_page=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/credits/history?page=example&per_page=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/credits/history?page=example&per_page=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Bundles

List bundles

GET /bundles

Operation ID: listBundles

Lists your bundles with their lifecycle status and the computed next_action (configure_account, configure_videos, publish_bundle, or null). next_action is the recommended way to find what a bundle still needs.

ParameterInRequiredDescription
pagequeryNoPage number.
per_pagequeryNoItems per page.
statusqueryNoFilter by bundle status.
bundle_typequeryNo
platformqueryNo
external_refqueryNo
account_statusqueryNo
StatusDescription
200Paginated bundle list.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/bundles?page=example&per_page=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles?page=example&per_page=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/bundles?page=example&per_page=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/bundles?page=example&per_page=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Create a bundle

POST /bundles

Operation ID: createBundle

Creates and pays an account-only, account-and-videos, or videos-only bundle in one atomic creation checkout. Credits are debited immediately on a successful POST, not when the bundle is published, and THERE IS NO CANCELLATION OR REFUND PATH — read getCreditCosts and getCreditBalance and confirm the total with the user before calling. One bundle produces exactly one account; order several accounts with createBundlesBulk. TikTok and Instagram only: a YouTube bundle is rejected with YOUTUBE_DELAYED even though getCreditCosts lists a YouTube price. Advanced Niche Warming requires wants_advanced_warming: true ALONGSIDE advanced_warming_terms or advanced_warming_terms_count; targets sent without the flag are refused with ADVANCED_WARMING_FLAG_REQUIRED, never charged and dropped. Send the term list or the count, not two quantities that disagree (ADVANCED_WARMING_COUNT_MISMATCH), and send distinct terms — anything removed by cleanup fails the call with ADVANCED_WARMING_TERMS_REJECTED rather than silently reducing what you receive and pay for. Credit cost is calculated server-side from the workspace cohort and any contract_bundle_allowance returned by getCreditCosts. A qualifying allowance slot is consumed in the same transaction as the credit debit; failed requests consume nothing. The exact new-customer and saved-account cutoff is the immutable production PRE-migration instant returned by getCreditCosts. A real TikTok or Instagram saved account created on or after that cutoff receives its first 30 days of TokPortal Coverage included unless the creation response marks it contractually exempt, and Coverage then recurs every 30 days per account at the managed_account_subscription price. For Advanced Niche Warming, pass advanced_warming_terms now, or buy a target count with advanced_warming_terms_count at the effective workspace rate and configure the targets later via PUT /bundles/{id}/warming-terms — one or the other, always with wants_advanced_warming: true. Set the targets BEFORE publishing: publishBundle refuses a bundle whose warming has no targets (ADVANCED_WARMING_NOT_CONFIGURED), and a purchase left unconfigured is not refunded. The bundle is created in pending_setup and still needs configureBundleAccount and publishBundle. Delivered videos are approved automatically unless you send auto_finalize_videos: false — the response echoes the settlement mode as review_mode ('auto' or 'manual'), plus a one-off review_mode_notice when you left the choice to the default. external_ref is a client correlation and duplicate-detection aid, not a retry mechanism; only Idempotency-Key provides exact request replay.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (CreateBundleRequest)

StatusDescription
201Bundle created.
400Invalid request body or business rule violation. Warming-specific codes: ADVANCED_WARMING_FLAG_REQUIRED (advanced_warming_terms or advanced_warming_terms_count sent without wants_advanced_warming: true), ADVANCED_WARMING_TERMS_REJECTED (a term was removed as a duplicate or for its length, so fewer targets would have been delivered and billed than requested — details.dropped_terms lists them), ADVANCED_WARMING_COUNT_MISMATCH (advanced_warming_terms and advanced_warming_terms_count state different quantities), ADVANCED_WARMING_TERMS (count outside 3-30 or not a multiple of 3), ADVANCED_WARMING_PLATFORM, WARMING_CONFLICT. UNKNOWN_FIELD when the body carries a field TokPortal does not recognise: this write is a full replace, so an unrecognised field is refused rather than dropped.
401Missing, invalid, or revoked API key.
402Insufficient credits.
409Duplicate account bundle, invalid state, or BUNDLE_PRICING_CHANGED. A pricing change charges nothing and creates nothing; fetch getCreditCosts again and retry with a new Idempotency-Key.
429Workspace bundle capacity is on cooldown. Retry later.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "bundle_type": "account_only"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "bundle_type": "account_only"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"bundle_type\": \"account_only\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles", strings.NewReader("{\n  \"bundle_type\": \"account_only\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Create bundles in bulk

POST /bundles/bulk

Operation ID: createBundlesBulk

Creates and pays several bundles in one atomic creation checkout. Credits are debited immediately on a successful POST, not at publication, and THERE IS NO CANCELLATION OR REFUND PATH — read getCreditCosts and getCreditBalance and confirm the total with the user before calling, because the loss is multiplied by accounts_count and platforms. Each bundle produces exactly one account. Warming targets and credits are charged per account, not per batch. Advanced Niche Warming requires wants_advanced_warming: true ALONGSIDE advanced_warming_terms or advanced_warming_terms_count; targets sent without the flag are refused with ADVANCED_WARMING_FLAG_REQUIRED for the whole batch, never charged and dropped. Send the term list or the count, not two quantities that disagree (ADVANCED_WARMING_COUNT_MISMATCH), and send distinct terms — anything removed by cleanup fails the call with ADVANCED_WARMING_TERMS_REJECTED. The complete batch is checked against rolling workspace capacity and any contract_bundle_allowance returned by getCreditCosts. Qualifying allowance slots are consumed in deterministic bundle-ID order in the same transaction as all debits; if any bundle fails, the entire batch rolls back and consumes nothing, so never split a 429 capacity-cooldown batch into parallel calls. A batch crossing the final allowance slot receives the contract price only for the remaining qualifying bundles. For Advanced Niche Warming, pass advanced_warming_terms, using the same terms for every account, or buy a per-account target count with advanced_warming_terms_count and configure each bundle's targets later via PUT /bundles/{id}/warming-terms — one or the other, always with wants_advanced_warming: true. Every bundle is returned in pending_setup with no username: each one still needs configureBundleAccount and publishBundle before any work starts. Delivered videos are approved automatically for every bundle in the batch unless you send auto_finalize_videos: false — the response echoes review_mode ('auto' or 'manual'), plus a one-off review_mode_notice when you left the choice to the default. external_ref is one optional batch-level reference copied to the resulting bundles, not a unique per-bundle key or a retry mechanism; use returned bundle IDs for identity and Idempotency-Key for exact replay.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (CreateBulkBundlesRequest)

StatusDescription
201Bulk bundle creation result.
400Invalid request body or business rule violation. Warming-specific codes: ADVANCED_WARMING_FLAG_REQUIRED (advanced_warming_terms or advanced_warming_terms_count sent without wants_advanced_warming: true), ADVANCED_WARMING_TERMS_REJECTED (a term was removed as a duplicate or for its length, so fewer targets would have been delivered and billed than requested — details.dropped_terms lists them), ADVANCED_WARMING_COUNT_MISMATCH (advanced_warming_terms and advanced_warming_terms_count state different quantities), ADVANCED_WARMING_TERMS (count outside 3-30 or not a multiple of 3), ADVANCED_WARMING_PLATFORM, WARMING_CONFLICT. UNKNOWN_FIELD when the body carries a field TokPortal does not recognise: this write is a full replace, so an unrecognised field is refused rather than dropped.
401Missing, invalid, or revoked API key.
402Insufficient credits.
409BUNDLE_PRICING_CHANGED means the full batch was rolled back with no charge and no allowance consumption. In details, required is the recomputed price of ONE bundle (the first that disagreed) while quoted is the total for the whole batch — required_scope and quoted_scope label which is which, and batch_bundles carries the count. Fetch getCreditCosts again and retry with a new Idempotency-Key.
429The full batch would exceed rolling workspace capacity. Retry later.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/bulk" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "platforms": [
    "tiktok"
  ],
  "country": "USA",
  "accounts_count": 25
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/bulk', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "platforms": [
    "tiktok"
  ],
  "country": "USA",
  "accounts_count": 25
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/bulk',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"platforms\": [\n    \"tiktok\"\n  ],\n  \"country\": \"USA\",\n  \"accounts_count\": 25\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/bulk", strings.NewReader("{\n  \"platforms\": [\n    \"tiktok\"\n  ],\n  \"country\": \"USA\",\n  \"accounts_count\": 25\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Get a bundle

GET /bundles/{id}

Operation ID: getBundle

Full state of one bundle: status, account configuration, video slot counts, warming purchase, cancellation and remake history, and next_action. A remade bundle keeps the same id while its saved_account_id changes.

ParameterInRequiredDescription
idpathYesBundle ID.
StatusDescription
200Bundle details.
401Missing, invalid, or revoked API key.
404Bundle not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Update bundle settings

PATCH /bundles/{id}

Operation ID: updateBundle

Updates mutable bundle metadata: title, external_ref, and auto_finalize_videos. auto_finalize_videos true (the default) approves every delivered video automatically — it counts as final on arrival; set it to false to hold each delivered video for your review, approving with finalizeBundleVideo or pushing back with requestBundleVideoCorrections yourself. Note that anything left in review is auto-finalized about 72 hours after entering it either way.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (PatchBundleRequest)

StatusDescription
200Bundle updated.
400Invalid patch body.
401Missing, invalid, or revoked API key.
403Bundle belongs to another account.
404Bundle not found.
409Duplicate external_ref.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X PATCH "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "auto_finalize_videos": false,
  "external_ref": "external_ref",
  "title": "Launch campaign"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c', {
  method: 'PATCH',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "auto_finalize_videos": false,
  "external_ref": "external_ref",
  "title": "Launch campaign"
})
});
const data = await response.json();

Python



response = requests.request(
    'PATCH',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"auto_finalize_videos\": false,\n  \"external_ref\": \"external_ref\",\n  \"title\": \"Launch campaign\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("PATCH", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c", strings.NewReader("{\n  \"auto_finalize_videos\": false,\n  \"external_ref\": \"external_ref\",\n  \"title\": \"Launch campaign\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Publish a bundle

POST /bundles/{id}/publish

Operation ID: publishBundle

Submits a fully configured bundle to the account-manager marketplace, which is what actually starts the work. Requires status pending_setup, a complete account profile, and at least one configured video on an account_and_videos bundle — call getBundlePublishReadiness first, it returns every blocker without debiting. A due Coverage period renews here, so this call can debit credits even though the bundle's own cost was charged at creation. Irreversible once a manager accepts: unpublishBundle only works before that, and never refunds. Publish-time date guard: any slot whose target date has already reached today or the past is moved forward to the earliest allowed day (today+1 with a delivered or existing account, today+3 while the account is still being created, in UTC), respecting the 3-videos-per-day-per-bundle cap — an overflowing day spills to the next one. Slots still in the future are NOT moved. This never fails the publish. When something moved, the response carries adjusted_videos — one entry per moved slot, {video_id, position, previous_date, new_date} — and adjusted_videos_note. Read them back instead of assuming the schedule you sent survived. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Publish result.
401Missing, invalid, or revoked API key.
402Insufficient credits.
404Bundle not found.
409Bundle is not ready or is in an invalid state. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.
429Workspace publication capacity is on cooldown. Retry later.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/publish" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/publish', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/publish',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/publish", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Check bundle publish readiness

GET /bundles/{id}/publish-readiness

Operation ID: getBundlePublishReadiness

Returns every current publish blocker without mutating the bundle, settling Coverage, or debiting credits. MANAGED_ACCOUNT_TASK_BLOCKED includes details.account_id and details.reason. Fetch GET /accounts/{account_id}/managed-subscription for the authoritative state and exact reactivation quote. A due included or active period can temporarily appear blocked until renewal is settled by cron or an approved mutation.

ParameterInRequiredDescription
idpathYesBundle ID.
StatusDescription
200Readiness blockers and ready flag.
401Missing, invalid, or revoked API key.
404Bundle not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/publish-readiness" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/publish-readiness', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/publish-readiness',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/publish-readiness", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Unpublish a bundle

POST /bundles/{id}/unpublish

Operation ID: unpublishBundle

Returns a published bundle to pending_setup so its account configuration can be edited again. Only works while the bundle is published and no account manager has accepted it (409 otherwise). Credits are NOT refunded and this is not a cancellation: there is no API path to cancel a bundle or recover its credits.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Unpublish result.
401Missing, invalid, or revoked API key.
404Bundle not found.
409Bundle is in an invalid state.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/unpublish" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/unpublish', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/unpublish',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/unpublish", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Add video slots to a bundle

POST /bundles/{id}/add-video-slots

Operation ID: addVideoSlots

Buys additional video slots on an existing bundle and debits credits immediately (see video_upload in getCreditCosts). The slots are non-refundable and there is no cancellation path for this purchase — confirm the exact cost with the user before calling. Configure the new positions with configureBundleVideo, then publish them.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (QuantityRequest)

StatusDescription
200Created video slots.
400Invalid quantity or bundle type.
401Missing, invalid, or revoked API key.
402Insufficient credits.
404Bundle not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/add-video-slots" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "quantity": 1
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/add-video-slots', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "quantity": 1
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/add-video-slots',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"quantity\": 1\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/add-video-slots", strings.NewReader("{\n  \"quantity\": 1\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Add VIDEO-editing slots to a bundle

POST /bundles/{id}/add-edit-slots

Operation ID: addEditSlots

Buys video-editing slots — professional editing of a video by the account manager — and debits credits immediately (see video_edit in getCreditCosts). Non-refundable, with no cancellation path; confirm the exact cost with the user before calling. edits_quantity cannot exceed the bundle's videos_quantity, one video consumes at most one slot, and a slot is only consumed when that video carries editing_instructions. This has nothing to do with editing an account profile — use createAccountEditRequest for that.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (QuantityRequest)

StatusDescription
200Created edit slots.
400Invalid quantity.
401Missing, invalid, or revoked API key.
402Insufficient credits.
404Bundle not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/add-edit-slots" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "quantity": 1
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/add-edit-slots', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "quantity": 1
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/add-edit-slots',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"quantity\": 1\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/add-edit-slots", strings.NewReader("{\n  \"quantity\": 1\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Account Configuration

Get bundle account configuration

GET /bundles/{id}/account

Operation ID: getBundleAccount

Returns the current account-profile configuration of a bundle (username, visible name, biography, picture) and its listing status. Read it before configureBundleAccount to see what is already set and whether the profile is still editable.

ParameterInRequiredDescription
idpathYesBundle ID.
StatusDescription
200Account configuration.
401Missing, invalid, or revoked API key.
404Bundle or account not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Configure bundle account profile

PUT /bundles/{id}/account

Operation ID: configureBundleAccount

Sets the profile of an account that has NOT been delivered yet: username, visible name, biography, profile picture, and link-in-bio on Instagram bundles. Only valid while the bundle is pending_setup or configured — unpublish first to edit after publishing, and use createAccountEditRequest for an account that is already delivered. This is a full replacement: any field you omit is written back as null, so re-send the whole profile when you change one part of it. link_in_bio is stored on Instagram bundles only. On any other platform it is accepted but not stored, and the response says so explicitly through _warnings while echoing link_in_bio: null — read both back. For backward compatibility this endpoint also accepts the exact purchased advanced_warming_terms list, but PUT /bundles/{id}/warming-terms is the recommended one-shot deferred-target endpoint because it remains available at any account status. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription. A new account that has not produced a saved account yet remains configurable before delivery.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (ConfigureAccountRequest)

StatusDescription
200Configured account.
400Invalid account profile, or advanced_warming_terms does not match the purchased target count (ADVANCED_WARMING_TERMS). UNKNOWN_FIELD when the body carries an unrecognised field: this is a full replace, so a misspelled field would otherwise erase the value it was meant to set.
401Missing, invalid, or revoked API key.
404Bundle or account not found, or no warming session to configure (WARMING_SESSION_NOT_FOUND).
409Account status does not allow configuration, or the warming targets are already set (WARMING_TERMS_ALREADY_SET). If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X PUT "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "username": "USA",
  "visible_name": "visible_name"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account', {
  method: 'PUT',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "username": "USA",
  "visible_name": "visible_name"
})
});
const data = await response.json();

Python



response = requests.request(
    'PUT',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"username\": \"USA\",\n  \"visible_name\": \"visible_name\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("PUT", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account", strings.NewReader("{\n  \"username\": \"USA\",\n  \"visible_name\": \"visible_name\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Request account corrections

POST /bundles/{id}/account/corrections

Operation ID: requestBundleAccountCorrections

Moves an in-review account back to pending corrections with reviewer feedback.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (AccountCorrectionsRequest)

StatusDescription
200Account corrections requested.
400Invalid correction body.
401Missing, invalid, or revoked API key.
404Bundle or account not found.
409Account is not in review.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account/corrections" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "comment": "Please review this item."
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account/corrections', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "comment": "Please review this item."
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account/corrections',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"comment\": \"Please review this item.\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account/corrections", strings.NewReader("{\n  \"comment\": \"Please review this item.\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Finalize account review

POST /bundles/{id}/account/finalize

Operation ID: finalizeBundleAccount

Approves an in-review account and marks it finalized.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Account finalized.
401Missing, invalid, or revoked API key.
404Bundle or account not found.
409Account is not in review.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account/finalize" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account/finalize', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account/finalize',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/account/finalize", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Videos

List bundle video slots

GET /bundles/{id}/videos

Operation ID: listBundleVideos

Lists every video slot of a bundle with its position, configuration, schedule and review status. Positions are 1-based and run up to the bundle's videos_quantity.

ParameterInRequiredDescription
idpathYesBundle ID.
StatusDescription
200Video slots for the bundle.
401Missing, invalid, or revoked API key.
404Bundle not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Get video slot configuration

GET /bundles/{id}/videos/{position}

Operation ID: getBundleVideo

Returns one video slot's configuration, schedule, review status and post link. Use it to check whether a slot is still inside its review window before finalizing it.

ParameterInRequiredDescription
idpathYesBundle ID.
positionpathYes1-based video slot position.
StatusDescription
200Video slot configuration.
401Missing, invalid, or revoked API key.
404Bundle or video slot not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Configure a video slot

PUT /bundles/{id}/videos/{position}

Operation ID: configureBundleVideo

Configures one slot as a video, carousel, or story, and schedules it. A story (TikTok/Instagram only) is a single video OR a single image (provide exactly one of video_url or story_image_url), has no description, and is verified by an account-manager screenshot. Platform-specific fields are only stored on their own platform: tiktok_sound_url on TikTok, every instagram_* field on Instagram, every youtube_* field on YouTube. A field belonging to another platform is accepted and not stored, but it is no longer discarded in silence: the response lists those names in ignored_fields, next to platform, with an ignored_fields_note explaining why. Check ignored_fields on every 200 — an empty or absent list is the only proof that everything you sent landed. Some options are paid and charged the first time they are set, never refunded: story_repost_url, instant_repost_as_story, and setting either volume field. Scheduling: target_publish_date is the FIRST day of a 2-day publishing window, never a fixed date — the end day is derived as that day + 1 and cannot be chosen, so target_publish_end_date is refused here with a hint pointing at PATCH. The window that was actually stored comes back as target_publish_start_date / target_publish_end_date. auto_publish is per-video HERE and top-level-only on the batch endpoint. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
positionpathYes1-based video slot position.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (ConfigureVideoRequest)

StatusDescription
200Configured video slot.
400Invalid video metadata.
401Missing, invalid, or revoked API key.
404Bundle or video slot not found.
409Video status does not allow configuration. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X PUT "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "video_type": "video",
  "target_publish_date": "2026-06-01"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1', {
  method: 'PUT',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "video_type": "video",
  "target_publish_date": "2026-06-01"
})
});
const data = await response.json();

Python



response = requests.request(
    'PUT',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"video_type\": \"video\",\n  \"target_publish_date\": \"2026-06-01\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("PUT", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1", strings.NewReader("{\n  \"video_type\": \"video\",\n  \"target_publish_date\": \"2026-06-01\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Patch video metadata or schedule

PATCH /bundles/{id}/videos/{position}

Operation ID: patchBundleVideo

Changes a configured slot's name, description, external reference or schedule without re-sending its media. Rescheduling obeys the maximum of 3 videos per day per bundle AND — enforced since 2026-08-26, where before only the per-day cap ran — the same minimum publish lead time as configuration: today+1 with a delivered or existing account, today+3 while the account is still being created, in UTC. Today and any past date are rejected with INVALID_DATE carrying min_days_ahead and earliest_allowed (YYYY-MM-DD), the same shape the configure PUT returns. Send either target_publish_date (a 2-day window whose end is derived as that day + 1) or the target_publish_start_date/target_publish_end_date pair, never both — this is the ONLY endpoint where the window's end can be chosen, and it must fall strictly after the start. Cannot be used once the video is finalized. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
positionpathYes1-based video slot position.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (PatchVideoRequest)

StatusDescription
200Patched video slot.
400Invalid patch body.
401Missing, invalid, or revoked API key.
404Bundle or video slot not found.
409Video status or account state does not allow this patch. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X PATCH "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "external_ref": "external_ref",
  "name": "name",
  "description": "Campaign content description.",
  "target_publish_date": "2026-06-01"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1', {
  method: 'PATCH',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "external_ref": "external_ref",
  "name": "name",
  "description": "Campaign content description.",
  "target_publish_date": "2026-06-01"
})
});
const data = await response.json();

Python



response = requests.request(
    'PATCH',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"external_ref\": \"external_ref\",\n  \"name\": \"name\",\n  \"description\": \"Campaign content description.\",\n  \"target_publish_date\": \"2026-06-01\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("PATCH", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1", strings.NewReader("{\n  \"external_ref\": \"external_ref\",\n  \"name\": \"name\",\n  \"description\": \"Campaign content description.\",\n  \"target_publish_date\": \"2026-06-01\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Configure video slots in bulk

PUT /bundles/{id}/videos/batch

Operation ID: batchConfigureBundleVideos

Configures many video slots in one call after a single account-level Coverage preflight. Partial success: this call returns HTTP 200 even when some or all rows are rejected. Read configured, results and errors[] and never infer success from the status code — rows breaking the 3-videos-per-day cap or the minimum lead time fail individually while the rest succeed, and the paid options they carry are only charged for the rows that succeeded. An auto_publish failure never fails the request; inspect auto_publish.blockers. TOP-LEVEL ONLY on this endpoint: one publish attempt per call, applying to every video in it. auto_publish inside videos[] is rejected with a hint telling you to move it out — it is per-video only on the single-slot PUT /bundles/{id}/videos/{position}. Each row's target_publish_date is the first day of a 2-day window whose end is derived (that day + 1); target_publish_end_date is refused on this endpoint. Publish-time date guard: any slot whose target date has already reached today or the past is moved forward to the earliest allowed day (today+1 with a delivered or existing account, today+3 while the account is still being created, in UTC), respecting the 3-videos-per-day-per-bundle cap — an overflowing day spills to the next one. Slots still in the future are NOT moved. This never fails the publish. When something moved, the response carries adjusted_videos — one entry per moved slot, {video_id, position, previous_date, new_date} — and adjusted_videos_note. Read them back instead of assuming the schedule you sent survived. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (BatchConfigureVideosRequest)

StatusDescription
200Batch video configuration result.
400Invalid batch body.
401Missing, invalid, or revoked API key.
404Bundle not found.
409One or more video slots or the account state does not allow configuration. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X PUT "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/batch" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "videos": [
    {
      "video_type": "video",
      "target_publish_date": "2026-06-01",
      "position": 1
    }
  ]
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/batch', {
  method: 'PUT',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "videos": [
    {
      "video_type": "video",
      "target_publish_date": "2026-06-01",
      "position": 1
    }
  ]
})
});
const data = await response.json();

Python



response = requests.request(
    'PUT',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/batch',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"videos\": [\n    {\n      \"video_type\": \"video\",\n      \"target_publish_date\": \"2026-06-01\",\n      \"position\": 1\n    }\n  ]\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("PUT", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/batch", strings.NewReader("{\n  \"videos\": [\n    {\n      \"video_type\": \"video\",\n      \"target_publish_date\": \"2026-06-01\",\n      \"position\": 1\n    }\n  ]\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Publish all configured videos on an active bundle

POST /bundles/{id}/videos/publish-all

Operation ID: publishAllBundleVideos

Hands every configured, unpublished slot on an active bundle to the account manager for posting. Publishing is irreversible once a manager has posted; call unscheduleBundleVideo beforehand to hold a slot back. Slots whose target date has already reached today or the past are moved forward to the earliest allowed day, respecting the 3-videos-per-day-per-bundle cap (an overflowing day spills to the next); slots still in the future are left alone, and the publish never fails because of it. When something moved, the response carries dates_adjusted (how many), adjusted_videos (one {video_id, position, previous_date, new_date} per moved slot) and hint. There is no earliest_allowed_date field on this response — it was documented but never returned, and adjusted_videos replaces it with the per-slot dates. Read them back instead of assuming the original schedule survived. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Published video slots.
401Missing, invalid, or revoked API key.
404Bundle not found.
409Bundle status does not allow publishing videos. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/publish-all" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/publish-all', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/publish-all',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/publish-all", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Import video slots from CSV

POST /bundles/{id}/videos/import-csv

Operation ID: importBundleVideosCsv

Uploads a CSV file, downloads the referenced media, and configures the available video slots. Partial success: this call returns HTTP 200 even when some or all rows are rejected. Read imported, results and errors[] and never infer success from the status code — rows breaking the 3-videos-per-day cap or the minimum lead time fail individually while the rest succeed. An auto_publish failure never fails the request; inspect auto_publish.blockers, and auto_publish.adjusted_videos when a stale date was moved forward at publish time. The date column is target_publish_start_date (note the name: the JSON endpoints call the same value target_publish_date) and it is the FIRST day of a 2-day window — the end is derived as that day + 1, so a target_publish_end_date column is refused with CSV_PARSE_ERROR rather than dropped. auto_publish is a form field on the multipart body and covers the whole import. Coverage is checked before TokPortal fetches or stores any referenced media. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: multipart/form-data (multipart form)

StatusDescription
200CSV import result.
400Invalid CSV or upload body.
401Missing, invalid, or revoked API key.
404Bundle not found.
409The account or bundle state does not allow CSV import. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/import-csv" \
  -H "X-API-Key: sk_your_key_here" \
  -F "file=@/path/to/file"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/import-csv', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/import-csv',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/import-csv", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Publish one video slot

POST /bundles/{id}/videos/{position}/publish

Operation ID: publishBundleVideo

Hands one configured slot to the account manager for posting on its target date. Irreversible once the manager has posted. If that target date has already reached today or the past it is moved forward to the earliest allowed day rather than published as instantly-due work, and the response then carries date_adjusted: true, original_date, new_date (both YYYY-MM-DD) and hint. Those four fields were documented long before they were real: until 2026-08-26 the RPC never returned them, and the slot published on its stale date. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
positionpathYes1-based video slot position.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Video slot published.
401Missing, invalid, or revoked API key.
404Bundle or video slot not found.
409Bundle or video status does not allow publishing. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/publish" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/publish', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/publish',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/publish", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Reset one video slot

POST /bundles/{id}/videos/{position}/reset

Operation ID: resetBundleVideo

Clears a slot's configuration so it can be configured again. Only works from pending or configured: to clear a published or accepted slot call unscheduleBundleVideo first, then reset. The slot's credits are not refunded and any paid option already charged on it stays charged. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
positionpathYes1-based video slot position.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Video slot reset.
401Missing, invalid, or revoked API key.
404Bundle or video slot not found.
409Video status does not allow reset. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/reset" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/reset', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/reset',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/reset", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Unschedule one video slot

POST /bundles/{id}/videos/{position}/unschedule

Operation ID: unscheduleBundleVideo

Pulls a published slot back out of the account-manager queue and returns it to configured, so it can be rescheduled or reset. Does not refund the slot. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
positionpathYes1-based video slot position.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Video slot unscheduled.
401Missing, invalid, or revoked API key.
404Bundle or video slot not found.
409Video status does not allow unscheduling. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/unschedule" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/unschedule', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/unschedule',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/unschedule", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Finalize video review

POST /bundles/{id}/videos/{position}/finalize

Operation ID: finalizeBundleVideo

Approves a delivered video and releases the account manager's payout — the review window cannot be reopened afterwards. Only valid while the slot is in_review. Any slot left in review is auto-finalized about 72 hours after entering it, so act inside that window. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
positionpathYes1-based video slot position.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Video slot finalized.
401Missing, invalid, or revoked API key.
404Bundle or video slot not found.
409Video is not in review. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/finalize" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/finalize', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/finalize',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/finalize", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Request video corrections

POST /bundles/{id}/videos/{position}/corrections

Operation ID: requestBundleVideoCorrections

Sends a delivered video back to the account manager with written feedback instead of approving it. Only valid while the slot is in_review, i.e. inside the ~72-hour window before it auto-finalizes. The response does not echo fields, so keep your own record of what you flagged. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
positionpathYes1-based video slot position.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (VideoCorrectionsRequest)

StatusDescription
200Video corrections requested.
400Invalid correction body.
401Missing, invalid, or revoked API key.
404Bundle or video slot not found.
409Video is not in review. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/corrections" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "comment": "Please review this item."
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/corrections', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "comment": "Please review this item."
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/corrections',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"comment\": \"Please review this item.\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/corrections", strings.NewReader("{\n  \"comment\": \"Please review this item.\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Fix a broken video download

POST /bundles/{id}/videos/{position}/fix-download

Operation ID: fixBundleVideoDownload

Replaces a manager-flagged broken video or carousel download URL and clears the download-issue flag. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription.

ParameterInRequiredDescription
idpathYesBundle ID.
positionpathYes1-based video slot position.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (FixVideoDownloadRequest)

StatusDescription
200Video download issue fixed.
400Invalid replacement media body.
401Missing, invalid, or revoked API key.
404Bundle or video slot not found.
409Video does not have a download issue. If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/fix-download" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "video_url": "https://example.com/resource"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/fix-download', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "video_url": "https://example.com/resource"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/fix-download',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"video_url\": \"https://example.com/resource\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/videos/1/fix-download", strings.NewReader("{\n  \"video_url\": \"https://example.com/resource\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Request an ad code (TikTok Spark Code / Instagram Partner Code) for a finalized video

POST /videos/{id}/ad-code-request

Operation ID: createVideoAdCodeRequest

Requests an ad code (TikTok Spark Code or Instagram Partner Code) for one finalized video and debits 7 credits, which are not refundable. The platform is resolved from the video itself and cannot be chosen. The video must be finalized on TikTok or Instagram with a live post link. Active TokPortal Coverage and a routable active account manager are required. TokPortal uses the account's current active manager, then its still-active manager relationship, then eligible non-cancelled order history. Completed bundles remain eligible without a delivery-age limit; cancelled orders are never restored. Assignment, debit, and task creation are atomic. Only one open request per video is allowed, so a request made on the wrong video cannot be replaced until it closes. Poll the GET endpoint to retrieve the code once the manager submits it.

ParameterInRequiredDescription
idpathYesVideo ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (AdCodeRequest)

StatusDescription
201Ad code request created.
400Invalid request or platform not available.
401Missing, invalid, or revoked API key.
402Insufficient credits.
404Video not found.
409Coverage is inactive, no active account manager or non-cancelled support order is available, the video is not finalized, or an open request already exists. No credits are charged on these failures.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/ad-code-request" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "note": "note"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/ad-code-request', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "note": "note"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/ad-code-request',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"note\": \"note\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/ad-code-request", strings.NewReader("{\n  \"note\": \"note\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Get / poll the ad code for a video

GET /videos/{id}/ad-code-request

Operation ID: getVideoAdCodeRequest

Returns the latest ad code request for the video. The code field is null until the manager submits it (status in_review) or it is delivered (status finalized).

ParameterInRequiredDescription
idpathYesVideo ID.
StatusDescription
200Latest ad code request (or null).
401Missing, invalid, or revoked API key.
404Video not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/ad-code-request" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/ad-code-request', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/ad-code-request',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/ad-code-request", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Analytics

Check analytics refresh availability

GET /accounts/{id}/analytics/can-refresh

Operation ID: canRefreshAccountAnalytics

Reports whether a manual analytics refresh is currently allowed and when the next one will be. Call it before refreshAccountAnalytics / refreshAnalyticsAccount to avoid a 429 cooldown (6 h for connected accounts, 24 h for the public fallback).

ParameterInRequiredDescription
idpathYesSaved account ID.
StatusDescription
200Manual refresh availability. can_refresh is false and reason identifies the Coverage or analytics-plan blocker when refresh is unavailable.
401Missing, invalid, or revoked API key.
404Account not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/can-refresh" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/can-refresh', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/can-refresh',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/can-refresh", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Refresh account analytics

POST /accounts/{id}/analytics/refresh

Operation ID: refreshAccountAnalytics

Legacy account-analytics refresh path, kept for older integrations; new code should call refreshAnalyticsAccount. Comment collection is gated on three conditions: includeComments only runs when force is true, includePosts is not false, and the workspace holds the full analytics tier. The two you control are refusals, not silence — includeComments without force, or with includePosts false, fails with INVALID_FIELD for MCP callers and is reported for direct REST callers. The one you cannot control, the analytics tier, comes back on the response as applied.include_comments plus applied.include_comments_dropped_reason. The call can still return 200 with deduped: true and skippedReason: "recent_snapshot", which means nothing was refreshed. Check canRefreshAccountAnalytics first: a forced retry consumes the manual-refresh cooldown slot and the next one is rejected with 429. Refresh is blocked for revealed/detached, banned or inactive-Coverage accounts; permanently grandfathered accounts remain eligible.

ParameterInRequiredDescription
idpathYesSaved account ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (RefreshAnalyticsRequest)

StatusDescription
200Refresh result.
401Missing, invalid, or revoked API key.
403Plan does not include this analytics level.
404Account not found.
409Account refresh blocked. The response reason identifies the account or TokPortal Coverage state.
429Manual refresh cooldown active.
502Refresh provider failed.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/refresh" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "force": false,
  "includePosts": false,
  "includeComments": false,
  "forcePosts": false
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/refresh', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "force": false,
  "includePosts": false,
  "includeComments": false,
  "forcePosts": false
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/refresh',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"force\": false,\n  \"includePosts\": false,\n  \"includeComments\": false,\n  \"forcePosts\": false\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/refresh", strings.NewReader("{\n  \"force\": false,\n  \"includePosts\": false,\n  \"includeComments\": false,\n  \"forcePosts\": false\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Get analytics dashboard

GET /analytics

Operation ID: getAnalyticsDashboard

Portfolio-level analytics across your accounts for a date range. Call getAnalyticsContract before interpreting these numbers: top-line values are lifetime per-post totals, connected-account values are rolling windows, the two intentionally do not mirror each other, and your access tier may redact metrics — a missing field means unavailable, never zero.

ParameterInRequiredDescription
workspacequeryNo
platformqueryNoRepeatable platform filter.
countryqueryNoRepeatable country filter.
accountqueryNoRepeatable account filter.
fromqueryNo
toqueryNo
StatusDescription
200Analytics dashboard data.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/analytics?workspace=example&platform=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics?workspace=example&platform=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/analytics?workspace=example&platform=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/analytics?workspace=example&platform=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Get analytics data contract

GET /analytics/contract

Operation ID: getAnalyticsContract

Returns the Analytics v2 contract, current access payload, metric semantics, freshness targets, and redaction rules.

StatusDescription
200Analytics contract and access payload.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/analytics/contract" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/contract', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/analytics/contract',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/analytics/contract", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Export analytics videos CSV

GET /analytics/export/videos

Operation ID: exportAnalyticsVideos

CSV export of post-level analytics for the selected accounts and date range. Requires the Creator tier or above. Call getAnalyticsContract before interpreting these numbers: top-line values are lifetime per-post totals, connected-account values are rolling windows, the two intentionally do not mirror each other, and your access tier may redact metrics — a missing field means unavailable, never zero.

ParameterInRequiredDescription
accountqueryNoRepeatable account filter.
workspacequeryNo
platformqueryNoRepeatable platform filter.
countryqueryNoRepeatable country filter.
qqueryNoSearch query.
fromqueryNo
toqueryNo
StatusDescription
200CSV export of analytics video rows.
401Missing, invalid, or revoked API key.
403Plan does not include this analytics level.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/analytics/export/videos?account=example&workspace=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/export/videos?account=example&workspace=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/analytics/export/videos?account=example&workspace=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/analytics/export/videos?account=example&workspace=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Create analytics web report

POST /analytics/export/reports

Operation ID: createAnalyticsReport

Creates a shareable Analytics v2 web report and returns its bearer-like access token and URL. The body is validated before anything is rendered, so a malformed request fails with INVALID_FIELD instead of quietly producing a wrong report for your end client. from and to are calendar dates in YYYY-MM-DD; a full ISO timestamp is accepted and truncated to its date, and anything else is refused rather than treated as "no filter" (which used to widen the report to the account's entire history). An inverted window (from after to) is refused too. template is a published enum — executive, agency, creator, minimal, growth, board — and an unknown value is refused instead of becoming executive. brandAccent must be #rrggbb (#rgb is expanded); named colours, rgb() and 8-digit hex are refused instead of being replaced by the TokPortal default. White-labelling still depends on entitlement: brandName and brandAccent only apply on the full analytics tier with a Farmer plan, and are otherwise reset. The response carries applied — the template, brand name, brand accent and the exact date window the report covers — plus canBrand, so read those back instead of inspecting the rendered report. Do not send Idempotency-Key. The successful response contains a secret and is never stored in the replay ledger. A request with the header is rejected with IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE (400) before any ledger claim or operation execution.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (CreateAnalyticsReportRequest)

StatusDescription
200Created analytics report.
400Invalid report request. Idempotency-Key is not supported because the successful response contains a secret. Remove the header before retrying. Error code: IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE.
401Missing, invalid, or revoked API key.
403Plan does not include this analytics level.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/analytics/export/reports" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Launch campaign",
  "template": "executive",
  "brandName": "brandName",
  "brandAccent": "brandAccent"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/export/reports', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "title": "Launch campaign",
  "template": "executive",
  "brandName": "brandName",
  "brandAccent": "brandAccent"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/analytics/export/reports',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"title\": \"Launch campaign\",\n  \"template\": \"executive\",\n  \"brandName\": \"brandName\",\n  \"brandAccent\": \"brandAccent\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/analytics/export/reports", strings.NewReader("{\n  \"title\": \"Launch campaign\",\n  \"template\": \"executive\",\n  \"brandName\": \"brandName\",\n  \"brandAccent\": \"brandAccent\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Export analytics report HTML

POST /analytics/export/reports/html

Operation ID: exportAnalyticsReportHtml

Creates a standalone downloadable HTML analytics report from the same body as createAnalyticsReport, and validates it identically: from and to are YYYY-MM-DD (a full ISO timestamp is truncated to its date, anything else is refused with INVALID_FIELD rather than treated as no filter), an inverted window is refused, template is the published enum (executive, agency, creator, minimal, growth, board) and an unknown value is refused instead of becoming executive, and brandAccent must be #rrggbb or #rgb. The body of the response is the HTML document itself, so what was applied travels in headers: X-TokPortal-Report-Template, X-TokPortal-Report-Can-Brand, X-TokPortal-Report-Brand-Name, X-TokPortal-Report-From, X-TokPortal-Report-To, alongside X-TokPortal-Export-Rows and X-TokPortal-Export-Accounts. Read those headers to confirm the window and the branding actually used.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (CreateAnalyticsReportRequest)

StatusDescription
200Standalone analytics report HTML.
400Invalid report request.
401Missing, invalid, or revoked API key.
403Plan does not include this analytics level.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/analytics/export/reports/html" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Launch campaign",
  "template": "executive",
  "brandName": "brandName",
  "brandAccent": "brandAccent"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/export/reports/html', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "title": "Launch campaign",
  "template": "executive",
  "brandName": "brandName",
  "brandAccent": "brandAccent"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/analytics/export/reports/html',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"title\": \"Launch campaign\",\n  \"template\": \"executive\",\n  \"brandName\": \"brandName\",\n  \"brandAccent\": \"brandAccent\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/analytics/export/reports/html", strings.NewReader("{\n  \"title\": \"Launch campaign\",\n  \"template\": \"executive\",\n  \"brandName\": \"brandName\",\n  \"brandAccent\": \"brandAccent\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Get analytics time series

GET /analytics/series

Operation ID: getAnalyticsSeries

Daily or weekly time series for one metric, built from stored snapshots and date-scoped rollups — never a live fetch. mode selects cumulative totals, per-period gains, or raw snapshot values. Series are excluded on the Starter tier. Call getAnalyticsContract before interpreting these numbers: top-line values are lifetime per-post totals, connected-account values are rolling windows, the two intentionally do not mirror each other, and your access tier may redact metrics — a missing field means unavailable, never zero.

ParameterInRequiredDescription
metricqueryNo
granularityqueryNo
modequeryNo
accountqueryNoRepeatable account filter.
fromqueryNo
toqueryNo
StatusDescription
200Analytics time series.
401Missing, invalid, or revoked API key.
403Plan does not include this analytics level.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/analytics/series?metric=example&granularity=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/series?metric=example&granularity=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/analytics/series?metric=example&granularity=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/analytics/series?metric=example&granularity=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Get account analytics drilldown

GET /analytics/accounts/{id}

Operation ID: getAnalyticsAccount

Analytics v2 drilldown for one account: current metrics, top posts and demographics. This is the current endpoint; getAccountAnalytics is the older compatibility shape of the same data. Call getAnalyticsContract before interpreting these numbers: top-line values are lifetime per-post totals, connected-account values are rolling windows, the two intentionally do not mirror each other, and your access tier may redact metrics — a missing field means unavailable, never zero.

ParameterInRequiredDescription
idpathYesSaved account ID.
StatusDescription
200Account analytics drilldown.
401Missing, invalid, or revoked API key.
404Account not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Refresh analytics account

POST /analytics/accounts/{id}/refresh

Operation ID: refreshAnalyticsAccount

Refreshes an owner-scoped account through Analytics v2. Comment collection is gated on three conditions: includeComments only runs when force is true, includePosts is not false, and the workspace holds the full analytics tier. The two you control are refusals, not silence — includeComments without force, or with includePosts false, fails with INVALID_FIELD for MCP callers and is reported for direct REST callers. The one you cannot control, the analytics tier, comes back on the response as applied.include_comments plus applied.include_comments_dropped_reason. The call can still return 200 with deduped: true and skippedReason: "recent_snapshot", which means nothing was refreshed. Check canRefreshAccountAnalytics first: a forced retry consumes the manual-refresh cooldown slot and the next one is rejected with 429. Refresh is blocked for revealed/detached, banned or inactive-Coverage accounts; permanently grandfathered accounts remain eligible.

ParameterInRequiredDescription
idpathYesSaved account ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (RefreshAnalyticsRequest)

StatusDescription
200Refresh result.
401Missing, invalid, or revoked API key.
403Plan does not include this analytics level.
404Account not found.
409Account refresh blocked. The response reason identifies the account or TokPortal Coverage state.
429Manual refresh cooldown active.
502Refresh provider failed.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/refresh" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "force": false,
  "includePosts": false,
  "includeComments": false,
  "forcePosts": false
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/refresh', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "force": false,
  "includePosts": false,
  "includeComments": false,
  "forcePosts": false
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/refresh',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"force\": false,\n  \"includePosts\": false,\n  \"includeComments\": false,\n  \"forcePosts\": false\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/refresh", strings.NewReader("{\n  \"force\": false,\n  \"includePosts\": false,\n  \"includeComments\": false,\n  \"forcePosts\": false\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

List raw account analytics snapshots

GET /analytics/accounts/{id}/raw

Operation ID: listAnalyticsAccountRawSnapshots

Returns owner-scoped stored raw analytics payloads for a saved account. Full analytics tier only.

ParameterInRequiredDescription
idpathYesSaved account ID.
sourcequeryNo
limitqueryNo
fromqueryNo
toqueryNo
StatusDescription
200Raw account analytics snapshots.
401Missing, invalid, or revoked API key.
403Plan does not include raw analytics payloads.
404Account not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/raw?source=example&limit=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/raw?source=example&limit=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/raw?source=example&limit=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/raw?source=example&limit=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Get account analytics compatibility view

GET /accounts/{id}/analytics

Operation ID: getAccountAnalytics

Legacy compatibility view of one account's analytics, kept for older integrations. New code should call getAnalyticsAccount instead. Call getAnalyticsContract before interpreting these numbers: top-line values are lifetime per-post totals, connected-account values are rolling windows, the two intentionally do not mirror each other, and your access tier may redact metrics — a missing field means unavailable, never zero.

ParameterInRequiredDescription
idpathYesSaved account ID.
StatusDescription
200Account analytics data.
401Missing, invalid, or revoked API key.
404Account not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

List post analytics for an account

GET /accounts/{id}/analytics/videos

Operation ID: listAccountVideoAnalytics

Post-level analytics for one account, sortable by views, likes, engagement rate or upload date. Values are lifetime per-post totals. Call getAnalyticsContract before interpreting these numbers: top-line values are lifetime per-post totals, connected-account values are rolling windows, the two intentionally do not mirror each other, and your access tier may redact metrics — a missing field means unavailable, never zero.

ParameterInRequiredDescription
idpathYesSaved account ID.
pagequeryNoPage number.
per_pagequeryNoItems per page.
sort_byqueryNo
sort_orderqueryNo
StatusDescription
200Post analytics list.
401Missing, invalid, or revoked API key.
404Account not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/videos?page=example&per_page=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/videos?page=example&per_page=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/videos?page=example&per_page=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics/videos?page=example&per_page=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Get single video analytics

GET /videos/{id}/analytics

Operation ID: getVideoAnalytics

Lifetime metrics for one tracked post. Interpret them with getAnalyticsContract: these are lifetime per-post totals and deliberately do not match the rolling-window figures shown for a connected account.

ParameterInRequiredDescription
idpathYesVideo ID.
StatusDescription
200Video analytics.
401Missing, invalid, or revoked API key.
404Video not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/videos/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/analytics", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Get comment pulse analytics

GET /analytics/comments

Operation ID: getCommentPulse

Aggregated comment sentiment and themes across your posts, served from the analytics comment cache — never a live fetch. Full analytics tier only. Filters are not combinable: when post is supplied it takes precedence and platform, country, account, workspace, from and to are all ignored, and at most 12 post ids are sampled.

ParameterInRequiredDescription
platformqueryNo
countryqueryNo
accountqueryNo
postqueryNo
limitqueryNo
postLimitqueryNo
fromqueryNo
toqueryNo
StatusDescription
200Comment pulse analytics.
401Missing, invalid, or revoked API key.
403Plan does not include this analytics level.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/analytics/comments?platform=example&country=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/comments?platform=example&country=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/analytics/comments?platform=example&country=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/analytics/comments?platform=example&country=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

List comments for an account post

GET /analytics/accounts/{id}/comments

Operation ID: listAnalyticsAccountComments

Returns cached comment text for one tracked post of an account. Full analytics tier only; this read never calls the platform live.

ParameterInRequiredDescription
idpathYesSaved account ID.
trackedPostIdqueryNo
postIdqueryNo
limitqueryNo
StatusDescription
200Post comments.
401Missing, invalid, or revoked API key.
403Plan does not include this analytics level.
404Account or post not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/comments?trackedPostId=example&postId=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/comments?trackedPostId=example&postId=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/comments?trackedPostId=example&postId=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/analytics/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/comments?trackedPostId=example&postId=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

List raw post analytics snapshots

GET /analytics/posts/{id}/raw

Operation ID: listAnalyticsPostRawSnapshots

Returns owner-scoped stored raw analytics payloads for a tracked post. Full analytics tier only.

ParameterInRequiredDescription
idpathYesTracked post ID.
sourcequeryNo
limitqueryNo
fromqueryNo
toqueryNo
StatusDescription
200Raw post analytics snapshots.
401Missing, invalid, or revoked API key.
403Plan does not include raw analytics payloads.
404Post not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/analytics/posts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/raw?source=example&limit=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/analytics/posts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/raw?source=example&limit=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/analytics/posts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/raw?source=example&limit=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/analytics/posts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/raw?source=example&limit=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Warming

Order Advanced Niche Warming (rewarm) on a delivered account

POST /accounts/{id}/rewarm

Operation ID: rewarmAccount

Starts an Advanced Niche Warming session on a saved account that is already delivered: for each provided term the manager screen-records a session that opens on the account profile (handle visible), searches the term on the account's platform, watches videos from the results, engages with them (likes/saves) and leaves a comment. Every recording is verified before completion. The standard rate is 5 credits per term, charged per target and never per day (3-30 terms, multiples of 3), with a 15-credit minimum; GET /credit-costs returns the effective rate. Once the session has tasks it is NEVER refunded, so confirm the total with the user before calling. Call listAccountWarmingSessions first: only one session can be active per account and a second one is rejected with 409 REWARM_ALREADY_ACTIVE. Requires active TokPortal Coverage, a routable active account manager backed by a non-cancelled support order, TikTok or Instagram, and no already-active warming session. A completed bundle remains eligible. Terms are split evenly over 3 calendar days in the manager's timezone; earlier-day tasks remain available until completed and sessions with tasks do not expire.

ParameterInRequiredDescription
idpathYesSaved account ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (RewarmAccountRequest)

StatusDescription
201Warming session created and started.
400Invalid terms (ADVANCED_WARMING_TERMS) or unsupported platform (ADVANCED_WARMING_PLATFORM).
401Missing, invalid, or revoked API key.
402Insufficient credits.
403Account belongs to another user.
404Account not found.
409No routable active account manager or non-cancelled support order (legacy code REWARM_NO_ACTIVE_ORDER), Coverage is inactive, or a warming session is already active (REWARM_ALREADY_ACTIVE). No credits are charged on these failures.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/rewarm" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "search_terms": [
    "search_terms 1",
    "search_terms 2",
    "search_terms 3"
  ]
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/rewarm', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "search_terms": [
    "search_terms 1",
    "search_terms 2",
    "search_terms 3"
  ]
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/rewarm',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"search_terms\": [\n    \"search_terms 1\",\n    \"search_terms 2\",\n    \"search_terms 3\"\n  ]\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/rewarm", strings.NewReader("{\n  \"search_terms\": [\n    \"search_terms 1\",\n    \"search_terms 2\",\n    \"search_terms 3\"\n  ]\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Configure the niche targets of a count-only Advanced Niche Warming purchase

PUT /bundles/{id}/warming-terms

Operation ID: configureBundleWarmingTerms

Only for a bundle created with advanced_warming_terms_count (deferred targets). To warm an account that is already delivered, use rewarmAccount instead; this endpoint answers 404 WARMING_SESSION_NOT_FOUND when there is no count-only purchase to configure. Must provide EXACTLY the purchased number of targets. One-shot: once configured the targets cannot be changed. Unlike PUT /bundles/{id}/account, this stays available at any account status after the manager accepted the mission or submitted the account, in which case the warming session starts immediately. Requires active TokPortal Coverage on the resolved saved account, or permanent grandfathering: a due period renews automatically at the account's stored rate immediately before execution, and nothing starts if it cannot renew. See getAccountManagedSubscription. A new account that has not produced a saved account yet remains configurable before delivery.

ParameterInRequiredDescription
idpathYesBundle ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (ConfigureWarmingTermsRequest)

StatusDescription
200Targets configured (started=true when the session began immediately).
400Wrong number of targets or invalid targets (ADVANCED_WARMING_TERMS), or an unrecognised field in the body (UNKNOWN_FIELD).
401Missing, invalid, or revoked API key.
404Bundle not found, or no warming session to configure (WARMING_SESSION_NOT_FOUND).
409Targets already configured (WARMING_TERMS_ALREADY_SET). If error.code is MANAGED_ACCOUNT_TASK_BLOCKED, read error.details.account_id and error.details.reason, fetch GET /accounts/{account_id}/managed-subscription, and reactivate only when that state is recoverable before retrying.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X PUT "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/warming-terms" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "advanced_warming_terms": [
    "advanced_warming_terms 1",
    "advanced_warming_terms 2",
    "advanced_warming_terms 3"
  ]
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/warming-terms', {
  method: 'PUT',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "advanced_warming_terms": [
    "advanced_warming_terms 1",
    "advanced_warming_terms 2",
    "advanced_warming_terms 3"
  ]
})
});
const data = await response.json();

Python



response = requests.request(
    'PUT',
    'https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/warming-terms',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"advanced_warming_terms\": [\n    \"advanced_warming_terms 1\",\n    \"advanced_warming_terms 2\",\n    \"advanced_warming_terms 3\"\n  ]\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("PUT", "https://app.tokportal.com/api/ext/bundles/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/warming-terms", strings.NewReader("{\n  \"advanced_warming_terms\": [\n    \"advanced_warming_terms 1\",\n    \"advanced_warming_terms 2\",\n    \"advanced_warming_terms 3\"\n  ]\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

List Advanced Niche Warming sessions for an account

GET /accounts/{id}/warming-sessions

Operation ID: listAccountWarmingSessions

Lists Advanced Niche Warming sessions (newest first) for a saved account you own, including per-term tasks, per-term reports, proof links, and the aggregated session report when completed. Tasks unlock over 3 calendar days in the manager's timezone and, once created, do not expire.

ParameterInRequiredDescription
idpathYesSaved account ID.
StatusDescription
200Warming sessions with tasks and reports.
401Missing, invalid, or revoked API key.
403Account belongs to another user.
404Account not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/warming-sessions" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/warming-sessions', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/warming-sessions',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/accounts/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/warming-sessions", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Get an Advanced Niche Warming session

GET /warming-sessions/{id}

Operation ID: getWarmingSession

Returns one Advanced Niche Warming session you own: status, per-term tasks (with 3-calendar-day dispatch in the manager's timezone, verification status, client report and proof video link), and the aggregated report on completion. Created tasks remain open until completed; they do not expire.

ParameterInRequiredDescription
idpathYesWarming session ID.
StatusDescription
200Warming session detail.
401Missing, invalid, or revoked API key.
404Warming session not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/warming-sessions/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/warming-sessions/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/warming-sessions/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/warming-sessions/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Generate Advanced Niche Warming search terms

POST /warming/generate-terms

Operation ID: generateWarmingTerms

Generates platform-appropriate Advanced Niche Warming search terms (niche targets) from a free-text niche description (max 1000 characters). This is free and no credits are charged. Call it before buying warming rather than inventing terms: pass the result to advanced_warming_terms (bundle creation or PUT /bundles/{id}/warming-terms deferred configuration) or to search_terms (rewarmAccount).

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (GenerateWarmingTermsRequest)

StatusDescription
200Generated search terms.
400Invalid count/description (ADVANCED_WARMING_TERMS) or unsupported platform (ADVANCED_WARMING_PLATFORM).
401Missing, invalid, or revoked API key.
502Term generation failed (TERM_GENERATION_FAILED).
503AI generation unavailable (AI_UNAVAILABLE).

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/warming/generate-terms" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "text": "text",
  "platform": "tiktok",
  "count": 27
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/warming/generate-terms', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "text": "text",
  "platform": "tiktok",
  "count": 27
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/warming/generate-terms',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"text\": \"text\",\n  \"platform\": \"tiktok\",\n  \"count\": 27\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/warming/generate-terms", strings.NewReader("{\n  \"text\": \"text\",\n  \"platform\": \"tiktok\",\n  \"count\": 27\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Webhooks

List webhook event catalog

GET /webhooks/events

Operation ID: listWebhookEvents

Returns the supported webhook event types with, for each one, its description, exact trigger condition, payload schema and an example payload, plus the delivery envelope and signature scheme. Read it before subscribing: several names are counter-intuitive — account.published fires when you publish the order, not when the account is delivered (that is account.in_review), and warming.session_started is not emitted for warming whose terms were set at bundle creation. This endpoint is public so teams can inspect webhook contracts before creating an API key.

StatusDescription
200Supported webhook events and delivery contract.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/webhooks/events"

Node

const response = await fetch('https://app.tokportal.com/api/ext/webhooks/events', {
  method: 'GET',
  headers: {


  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/webhooks/events',
    headers={}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/webhooks/events", nil)
resp, err := http.DefaultClient.Do(req)

List webhook endpoints

GET /webhooks

Operation ID: listWebhookEndpoints

Lists your webhook endpoints with their subscribed events and enabled state. Signing secrets are never returned here — only once, at creation.

ParameterInRequiredDescription
pagequeryNoPage number.
per_pagequeryNoItems per page.
enabledqueryNoFilter by enabled state.
eventqueryNoFilter endpoints subscribed to an event.
StatusDescription
200Paginated webhook endpoint list.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/webhooks?page=example&per_page=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/webhooks?page=example&per_page=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/webhooks?page=example&per_page=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/webhooks?page=example&per_page=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Create a webhook endpoint

POST /webhooks

Operation ID: createWebhookEndpoint

Creates a webhook endpoint and returns its signing secret once — store it, because a lost secret can only be replaced by deleting the endpoint and creating a new one. Call listWebhookEvents before choosing events: it returns the description, trigger condition and payload schema of each event, and several names are counter-intuitive. In particular account.published fires when YOU publish the order, not when the account is delivered — the delivery signal is account.in_review, and account.finalized fires when the account review is approved. Do not send Idempotency-Key. The successful response contains a secret and is never stored in the replay ledger. A request with the header is rejected with IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE (400) before any ledger claim or operation execution.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (CreateWebhookEndpointRequest)

StatusDescription
201Webhook endpoint created.
400Invalid webhook endpoint. Idempotency-Key is not supported because the successful response contains a secret. Remove the header before retrying. Error code: IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE.
401Missing, invalid, or revoked API key.
409Endpoint already exists.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/webhooks" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/resource",
  "events": [
    "webhook.test"
  ]
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/webhooks', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "url": "https://example.com/resource",
  "events": [
    "webhook.test"
  ]
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/webhooks',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"url\": \"https://example.com/resource\",\n  \"events\": [\n    \"webhook.test\"\n  ]\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/webhooks", strings.NewReader("{\n  \"url\": \"https://example.com/resource\",\n  \"events\": [\n    \"webhook.test\"\n  ]\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Get a webhook endpoint

GET /webhooks/{id}

Operation ID: getWebhookEndpoint

Returns one webhook endpoint's URL, subscribed events and enabled state. The signing secret is not returned; if you lost it, delete this endpoint and create a replacement.

ParameterInRequiredDescription
idpathYesWebhook endpoint ID.
StatusDescription
200Webhook endpoint details.
401Missing, invalid, or revoked API key.
404Webhook endpoint not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Update a webhook endpoint

PATCH /webhooks/{id}

Operation ID: updateWebhookEndpoint

Changes an endpoint's URL, subscribed events, description or enabled state. The signing secret is unchanged and is never re-returned. Call listWebhookEvents for the meaning, trigger condition and payload of each event type before changing the subscription list.

ParameterInRequiredDescription
idpathYesWebhook endpoint ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (UpdateWebhookEndpointRequest)

StatusDescription
200Webhook endpoint updated.
400Invalid webhook endpoint patch.
401Missing, invalid, or revoked API key.
404Webhook endpoint not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X PATCH "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/resource",
  "events": [
    "webhook.test"
  ],
  "description": "Campaign content description.",
  "enabled": false
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c', {
  method: 'PATCH',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "url": "https://example.com/resource",
  "events": [
    "webhook.test"
  ],
  "description": "Campaign content description.",
  "enabled": false
})
});
const data = await response.json();

Python



response = requests.request(
    'PATCH',
    'https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"url\": \"https://example.com/resource\",\n  \"events\": [\n    \"webhook.test\"\n  ],\n  \"description\": \"Campaign content description.\",\n  \"enabled\": false\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("PATCH", "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c", strings.NewReader("{\n  \"url\": \"https://example.com/resource\",\n  \"events\": [\n    \"webhook.test\"\n  ],\n  \"description\": \"Campaign content description.\",\n  \"enabled\": false\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Delete a webhook endpoint

DELETE /webhooks/{id}

Operation ID: deleteWebhookEndpoint

Permanently deletes a webhook endpoint and invalidates its signing secret. Events that could not be delivered are not queued for a replacement endpoint.

ParameterInRequiredDescription
idpathYesWebhook endpoint ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
204Webhook endpoint deleted.
401Missing, invalid, or revoked API key.
404Webhook endpoint not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X DELETE "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c', {
  method: 'DELETE',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'DELETE',
    'https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("DELETE", "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

List webhook deliveries

GET /webhooks/{id}/deliveries

Operation ID: listWebhookDeliveries

Lists delivery attempts for one endpoint with the response status and body. Deliveries are at-least-once and retries reuse the same TokPortal-Event-Id — deduplicate on that id, not on the delivery id.

ParameterInRequiredDescription
idpathYesWebhook endpoint ID.
pagequeryNoPage number.
per_pagequeryNoItems per page.
event_typequeryNoFilter by event type.
successqueryNoFilter by delivery success.
StatusDescription
200Paginated delivery attempts for the webhook endpoint.
401Missing, invalid, or revoked API key.
404Webhook endpoint not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/deliveries?page=example&per_page=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/deliveries?page=example&per_page=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/deliveries?page=example&per_page=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/deliveries?page=example&per_page=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Retry a webhook delivery

POST /webhooks/{id}/deliveries/{delivery_id}/retry

Operation ID: retryWebhookDelivery

Resends the stored webhook payload to the endpoint's current URL with a fresh TokPortal-Signature header. The event ID is preserved so receivers can keep idempotent processing.

ParameterInRequiredDescription
idpathYesWebhook endpoint ID.
delivery_idpathYesWebhook delivery ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Retry delivery result.
401Missing, invalid, or revoked API key.
404Webhook endpoint or delivery not found.
409Webhook endpoint is disabled or payload is unavailable.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/deliveries/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/retry" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/deliveries/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/retry', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/deliveries/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/retry',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/deliveries/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/retry", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Send a test webhook

POST /webhooks/{id}/test

Operation ID: testWebhookEndpoint

Sends a signed webhook.test event to the endpoint and records the delivery result.

ParameterInRequiredDescription
idpathYesWebhook endpoint ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Test delivery result.
401Missing, invalid, or revoked API key.
404Webhook endpoint not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/test" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/test', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/test',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/webhooks/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/test", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Uploads

Create a video upload URL

POST /upload/video

Operation ID: uploadVideo

Returns a short-lived presigned upload capability for a video file. The public_url it returns is what goes into video_url on configureBundleVideo — images are the opposite and require the upload response's storage_path. Do not send Idempotency-Key. The successful response contains a secret and is never stored in the replay ledger. A request with the header is rejected with IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE (400) before any ledger claim or operation execution.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (UploadVideoRequest)

StatusDescription
200Presigned video upload URL.
400Invalid upload request. Idempotency-Key is not supported because the successful response contains a secret. Remove the header before retrying. Error code: IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/upload/video" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "filename": "media.mp4",
  "bundle_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/upload/video', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "filename": "media.mp4",
  "bundle_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/upload/video',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"filename\": \"media.mp4\",\n  \"bundle_id\": \"9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/upload/video", strings.NewReader("{\n  \"filename\": \"media.mp4\",\n  \"bundle_id\": \"9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Upload a video file directly

POST /upload/video/direct

Operation ID: uploadVideoDirect

Uploads multipart/form-data directly through TokPortal and returns the public video URL, which goes straight into video_url on configureBundleVideo.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: multipart/form-data (multipart form)

StatusDescription
200Uploaded video URL.
400Invalid upload.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/upload/video/direct" \
  -H "X-API-Key: sk_your_key_here" \
  -F "file=@/path/to/file"

Node

const response = await fetch('https://app.tokportal.com/api/ext/upload/video/direct', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/upload/video/direct',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/upload/video/direct", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Create an image upload URL

POST /upload/image

Operation ID: uploadImage

Returns a short-lived signed upload URL and upload token for an image. Use the returned storage_path — NOT public_url — for carousel_images and profile_picture_url; videos are the opposite, uploadVideo returns a public_url that goes straight into video_url. The stored object's extension comes from the declared content_type (image/png stores a .png), and only falls back to the extension in filename when the content type is unknown, so send the content type that matches the bytes you are about to PUT. storage_path on the response is the key the object is actually stored under — read it rather than rebuilding it from filename. Do not send Idempotency-Key. The successful response contains a secret and is never stored in the replay ledger. A request with the header is rejected with IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE (400) before any ledger claim or operation execution.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (UploadImageRequest)

StatusDescription
200Signed image upload URL.
400Invalid upload request. Idempotency-Key is not supported because the successful response contains a secret. Remove the header before retrying. Error code: IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/upload/image" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "filename": "profile.png",
  "content_type": "image/png",
  "bundle_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/upload/image', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "filename": "profile.png",
  "content_type": "image/png",
  "bundle_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/upload/image',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"filename\": \"profile.png\",\n  \"content_type\": \"image/png\",\n  \"bundle_id\": \"9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/upload/image", strings.NewReader("{\n  \"filename\": \"profile.png\",\n  \"content_type\": \"image/png\",\n  \"bundle_id\": \"9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Upload an image file directly

POST /upload/image/direct

Operation ID: uploadImageDirect

Uploads multipart/form-data directly through TokPortal and returns storage details. Use the returned storage_path — NOT public_url — for carousel_images and profile_picture_url. HEIF/HEIC may be converted to JPEG. purpose is validated like it is on the JSON upload endpoints: an explicit value other than carousel or profile_picture is refused with INVALID_FIELD for MCP callers and reported in meta.ignored_fields for direct REST callers — it is no longer quietly rounded down to carousel. Omitting purpose still means carousel, which is the documented default. Confirm the destination with the returned bucket.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: multipart/form-data (multipart form)

StatusDescription
200Uploaded image URL.
400Invalid upload.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/upload/image/direct" \
  -H "X-API-Key: sk_your_key_here" \
  -F "file=@/path/to/file"

Node

const response = await fetch('https://app.tokportal.com/api/ext/upload/image/direct', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/upload/image/direct',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/upload/image/direct", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Import an image from URL

POST /upload/image/from-url

Operation ID: uploadImageFromUrl

Fetches a public direct image URL and stores it permanently in TokPortal storage. Use the returned storage_path — NOT public_url — for carousel_images and profile_picture_url.

ParameterInRequiredDescription
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (UploadImageFromUrlRequest)

StatusDescription
200Stored image details.
400Invalid image URL or request body.
401Missing, invalid, or revoked API key.
404Bundle not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/upload/image/from-url" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/resource",
  "bundle_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/upload/image/from-url', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "url": "https://example.com/resource",
  "bundle_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/upload/image/from-url',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"url\": \"https://example.com/resource\",\n  \"bundle_id\": \"9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/upload/image/from-url", strings.NewReader("{\n  \"url\": \"https://example.com/resource\",\n  \"bundle_id\": \"9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Comments

List comment tasks

GET /comments

Operation ID: listCommentTasks

Lists owned comment tasks. execution_blocked and execution_block_reason identify tasks paused by inactive TokPortal Coverage; reads remain available while execution is paused.

ParameterInRequiredDescription
pagequeryNoPage number.
per_pagequeryNoItems per page.
statusqueryNoFilter by one status or a comma-separated list of statuses.
saved_account_idqueryNoFilter by a user-owned saved account ID.
StatusDescription
200Paginated comment task list.
401Missing, invalid, or revoked API key.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/comments?page=example&per_page=example" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/comments?page=example&per_page=example', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/comments?page=example&per_page=example',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/comments?page=example&per_page=example", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Create comment tasks

POST /comments

Operation ID: createCommentTasks

Creates one comment task or a partial-success batch of up to 200. Each accepted task costs 1 credit. The target is any public TikTok or Instagram video, including a third party's — that is the intended use — while the posting account must be one of your own delivered accounts and its platform must match the target's (COMMENT_PLATFORM_MISMATCH otherwise). Deduplicate locally on (saved_account_id, target URL, comment_text): two different Idempotency-Keys carrying the same comment bill twice. The saved account must be client-owned, manager-assigned, and allowed to execute tasks by TokPortal Coverage, unless it is permanently grandfathered. TokPortal locks and revalidates the account and Coverage before the accepted-task debit and all accepted inserts commit in one transaction. Rejected rows are never charged. Read credits_charged instead of calculating it from the request, and read meta.rejected_count and each row's reason rather than inferring success from the 201. Send an Idempotency-Key and reuse the same key, method, path, and body after an uncertain transport result so the completed response is replayed without a second charge.

ParameterInRequiredDescription
Idempotency-KeyheaderNoStrongly recommended for this credit or workflow mutation. Use one unique key per logical request and reuse the same key only with the same method, path, query, and body after an uncertain result. Maximum 255 characters.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (CreateCommentTaskRequest)

StatusDescription
201Created and rejected comment-task rows.
400Invalid comment task request.
401Missing, invalid, or revoked API key.
402Insufficient credits.
409Single task blocked by account state or inactive TokPortal Coverage. For a batch, inspect each rejected row instead.
500The batch outcome could not be confirmed. Retry the exact request with the same Idempotency-Key; do not create a new logical request until reconciled.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/comments" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Idempotency-Key: createCommentTasks-example-1" \
  -H "Content-Type: application/json" \
  -d '{
  "saved_account_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c",
  "comment_text": "comment_text"
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/comments', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'createCommentTasks-example-1',
  },
  body: JSON.stringify({
  "saved_account_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c",
  "comment_text": "comment_text"
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/comments',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"], "Idempotency-Key": "createCommentTasks-example-1"},
    json=json.loads("{\n  \"saved_account_id\": \"9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c\",\n  \"comment_text\": \"comment_text\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/comments", strings.NewReader("{\n  \"saved_account_id\": \"9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c\",\n  \"comment_text\": \"comment_text\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Idempotency-Key", "createCommentTasks-example-1")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

Get a comment task

GET /comments/{id}

Operation ID: getCommentTask

Returns one owned comment task, including whether inactive TokPortal Coverage currently blocks manager execution and the stable block reason.

ParameterInRequiredDescription
idpathYesComment task ID.
StatusDescription
200Comment task.
401Missing, invalid, or revoked API key.
404Comment task not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Cancel a pending comment task

DELETE /comments/{id}

Operation ID: deleteCommentTask

Cancels an owned task only while its status is pending. The terminal transition and refund of the task's exact stored credit cost commit atomically and are idempotent. Current tasks refund 1 credit; credits_refunded remains authoritative for historical tasks. Cancellation remains available when Coverage is paused because it stops pending work rather than executing it. Reuse the same Idempotency-Key after an uncertain transport result.

ParameterInRequiredDescription
idpathYesComment task ID.
Idempotency-KeyheaderNoStrongly recommended for this credit or workflow mutation. Use one unique key per logical request and reuse the same key only with the same method, path, query, and body after an uncertain result. Maximum 255 characters.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Cancellation and exact refund result.
401Missing, invalid, or revoked API key.
404Comment task not found.
409Only a pending task can be cancelled.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X DELETE "https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Idempotency-Key: deleteCommentTask-example-1"

Node

const response = await fetch('https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c', {
  method: 'DELETE',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Idempotency-Key': 'deleteCommentTask-example-1',
  }
});
const data = await response.json();

Python



response = requests.request(
    'DELETE',
    'https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"], "Idempotency-Key": "deleteCommentTask-example-1"}
)
print(response.json())

Go

req, _ := http.NewRequest("DELETE", "https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Idempotency-Key", "deleteCommentTask-example-1")
resp, err := http.DefaultClient.Do(req)

Approve a manually confirmed comment task

POST /comments/{id}/approve

Operation ID: approveCommentTask

Approves and finalizes an owned manually_confirmed task. Execution is blocked while TokPortal Coverage is inactive; inspect MANAGED_ACCOUNT_TASK_BLOCKED and reactivate a recoverable account first.

ParameterInRequiredDescription
idpathYesComment task ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).
StatusDescription
200Approved comment task.
401Missing, invalid, or revoked API key.
404Comment task not found.
409Comment task status cannot be approved, or TokPortal Coverage currently blocks execution.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/approve" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/approve', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/approve',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/approve", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)

Dispute a manually confirmed comment task

POST /comments/{id}/dispute

Operation ID: disputeCommentTask

Returns an owned manually_confirmed task to pending corrections. Execution is blocked while TokPortal Coverage is inactive; inspect MANAGED_ACCOUNT_TASK_BLOCKED and reactivate a recoverable account first.

ParameterInRequiredDescription
idpathYesComment task ID.
X-TokPortal-Dry-RunheaderNoSend true (or 1/yes) to SIMULATE this call instead of executing it. A dry run performs the full validation and pricing path of the real operation and stops before the first write: no credits are debited, no rows are created, no webhooks fire, and no operator ever sees it. The response has the same shape and status as the real call and adds dry_run: true, dry_run_notice, and — on credit-spending operations — credits_would_charge carrying the real price. Errors are identical to the real call, which is the point: this is how you learn what an operation will do before paying for it. Rate limits are consumed normally, a supplied Idempotency-Key is ignored rather than burned, and identifiers returned by a dry run are synthetic (see DRY_RUN_ID_IN_LIVE_REQUEST).

Request body: application/json (DisputeCommentRequest)

StatusDescription
200Disputed comment task.
400Invalid dispute reason.
401Missing, invalid, or revoked API key.
404Comment task not found.
409Comment task status cannot be disputed, or TokPortal Coverage currently blocks execution.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.
Idempotent-ReplayedPresent with value true when a mutating request is replayed from a completed Idempotency-Key.
X-TokPortal-Dry-RunPresent and set to true when the request was simulated. This is the only dry-run marker on error responses, so a client can confirm it spent nothing without parsing the body.

Examples

curl

curl -X POST "https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/dispute" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Please review this item."
}'

Node

const response = await fetch('https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/dispute', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,
    'Content-Type': 'application/json',

  },
  body: JSON.stringify({
  "reason": "Please review this item."
})
});
const data = await response.json();

Python



response = requests.request(
    'POST',
    'https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/dispute',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]},
    json=json.loads("{\n  \"reason\": \"Please review this item.\"\n}")
)
print(response.json())

Go

req, _ := http.NewRequest("POST", "https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/dispute", strings.NewReader("{\n  \"reason\": \"Please review this item.\"\n}"))
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)

List comment task verification events

GET /comments/{id}/verifications

Operation ID: listCommentTaskVerifications

Lists verifier attempts for one owned task. This read remains available while TokPortal Coverage pauses task execution.

ParameterInRequiredDescription
idpathYesComment task ID.
StatusDescription
200Verification timeline.
401Missing, invalid, or revoked API key.
404Comment task not found.

Response headers

HeaderDescription
X-TokPortal-API-VersionCurrent TokPortal public API contract version.
X-TokPortal-API-StabilityStability channel for the public API contract.
X-TokPortal-Request-IDRequest correlation ID. Echoes a valid X-Request-ID request header or uses a generated req_ identifier.
X-RateLimit-LimitMaximum token bucket capacity for the authenticated API key.
X-RateLimit-RemainingApproximate requests remaining for the authenticated API key after this request.
X-RateLimit-ResetUnix timestamp at which the current one-minute budget window rolls over.
Retry-AfterSeconds to wait before retrying after a rate_limited response. Present on 429 responses only.

Examples

curl

curl -X GET "https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/verifications" \
  -H "X-API-Key: sk_your_key_here"

Node

const response = await fetch('https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/verifications', {
  method: 'GET',
  headers: {
    'X-API-Key': process.env.TOKPORTAL_API_KEY!,

  }
});
const data = await response.json();

Python



response = requests.request(
    'GET',
    'https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/verifications',
    headers={"X-API-Key": os.environ["TOKPORTAL_API_KEY"]}
)
print(response.json())

Go

req, _ := http.NewRequest("GET", "https://app.tokportal.com/api/ext/comments/9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c/verifications", nil)
req.Header.Set("X-API-Key", os.Getenv("TOKPORTAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)