Getting Started

Changelog

Public TokPortal API, SDK, CLI, MCP, and documentation changelog.

Changelog

2026-09-01

Sandbox: simulate any write for free

Credits are debited at creation and never refunded, which made the first call to an unfamiliar operation expensive to get wrong. Every non-GET operation now accepts X-TokPortal-Dry-Run: true (or dry_run: true as an MCP tool argument). The request runs the same validation and the same server-side pricing as the real call and stops before the first write.

  • Nothing happens. No credits debited, no allowance slot consumed, no row written, no webhook delivered, no notification — no account manager ever sees a simulated bundle. revealAccountCredentials and retrieveAccountVerificationCode return their 428 policy preview and never a credential, real or invented.
  • Everything is real except the write. Same status, same response shape, same errors with the same details. That is the point: you learn what an operation refuses before a real call refuses it.
  • The price comes back. Successful simulations carry credits_would_charge (the real price, workspace allowance included) next to credits_charged: 0 and your unchanged credits_remaining, plus dry_run: true and dry_run_notice. Every response, success and error, carries the X-TokPortal-Dry-Run: true response header — the only marker on an error body.
  • Whole flows chain. Identifiers returned by a dry run are synthetic, always starting 00000000-0000-4000-8000-, and are accepted by other dry-run calls so you can walk create → configure → publish. Send one to a real call and you get DRY_RUN_ID_IN_LIVE_REQUEST (400) instead of a confusing not-found.
  • Your Idempotency-Key survives. A key sent with a dry run is ignored, not consumed: the real call that follows with the same key still executes.
  • Rate limits are not bypassed. A simulation consumes the same bucket as a real call — the server work is real even when the write is not.

Full reference: Sandbox (dry run).

Corrections to the published contract

  • CreateBundleResponse and CreateBulkBundlesResponse described credits_charged, credits_remaining and cost_breakdown as nested inside a meta object. The wire format has always been flat, next to data, and the responses themselves never changed — only the schema was wrong, since its introduction on 2026-08-31. Corrected.
  • The MCP tool catalogue exposed the raw header name Idempotency-Key as a tool argument on two tools. It never did anything (the value became a query parameter the route ignores); the real argument has always been idempotency_key. Header parameters are no longer turned into tool arguments.

tokportal-mcp 1.15.0 carries the dry_run argument on all 50 non-GET tools.

2026-08-31

Video approval: automatic again by default, and the choice is now stated

For five days every new bundle held delivered videos for manual review. Measured against real behaviour, that default served almost nobody: 619 clients with a week or more of exposure never approved or corrected a single video, so their deliveries simply waited 72 hours before finalizing themselves. The 289 clients who do review keep manual review, and their existing bundles were untouched.

  • auto_finalize_videos defaults to true again on POST /bundles and POST /bundles/bulk. A delivered video is approved on arrival and counts as final. Send auto_finalize_videos: false to review each delivery — approving with POST /bundles/{id}/videos/{position}/finalize or pushing back with the corrections endpoint. This is changeable at any time with PATCH /bundles/{id}; it is not fixed at creation.
  • The creation responses now say which mode you got. Both create endpoints return review_mode: "auto" or "manual". When you omitted auto_finalize_videos, they also return review_mode_notice, one sentence stating that the default was applied and how to opt out. Send the field explicitly and the notice disappears — its presence means "you did not choose", never "something went wrong".
  • The contract describes it. CreateBundleResponse is a real schema instead of a free-form object, CreateBulkBundlesResponse exists, and both spell out what each mode settles to. The MCP tool catalogue was regenerated accordingly: tokportal-mcp 1.14.1 on npm and on the com.tokportal/mcp registry entry.
  • Unchanged: anything left in in_review is still finalized automatically about 72 hours after entering it, in either mode. auto_finalize_videos has never switched that off.

2026-08-26

Publish dates: rescheduling obeys the lead time, publishing repairs stale dates

The minimum publish lead time was only ever enforced when a date was first written. Two holes let videos reach a manager as instantly-due, same-day work. Both are closed, and the published contract — the OpenAPI descriptions, the MCP tool catalogue, this documentation — has been corrected to describe what the API actually does.

  • PATCH /bundles/{id}/videos/{position} now enforces the lead time. Rescheduling used to run only the 3-videos-per-day cap, so a PATCH could move a video onto today or into the past. It now applies the same rule as configuration — today + 1 with a delivered or existing account, today + 3 while the account is still being created, compared in UTC — and rejects anything earlier with INVALID_DATE carrying min_days_ahead and earliest_allowed. This is the same error shape the configure PUT already returned. If you re-date videos in bulk, especially onto the current day, this is the change most likely to turn a 200 into a 400. The MCP tool updateVideo and the Operator bulk date shift both funnel through this endpoint.
  • Publishing moves a stale date forward instead of shipping it. No publish path revalidated dates, and a draft that sat in pending_setup long enough published them as they were. Every publish surface — POST /bundles/{id}/publish, every auto_publish path (single configure, batch, CSV, on a bundle in setup or already active), POST /bundles/{id}/videos/{position}/publish, POST /bundles/{id}/videos/publish-all, and the dashboard — now moves any slot whose target date has already reached today or the past to the earliest allowed day, honouring the 3-per-day cap. Slots still in the future are not touched, and the guard never fails a publish: a call that used to succeed cannot start failing because of it.
  • What was moved is reported. POST /bundles/{id}/publish and the auto_publish object carry adjusted_videos — one { video_id, position, previous_date, new_date } entry per moved slot — plus adjusted_videos_note. The single-video publish carries date_adjusted, original_date, new_date and hint; publish-all carries dates_adjusted, adjusted_videos and hint. The single-video and publish-all fields had been documented for a long time, but the underlying procedures never returned them and the slots published on their stale dates. They are real now.
  • Removed: earliest_allowed_date on POST /bundles/{id}/videos/publish-all. It was documented and never returned. adjusted_videos[].new_date replaces it and says more: because the per-day cap can spread the moved slots over several days, one "earliest allowed" day could never describe the result.

target_publish_date is a window, and the contract now says so

target_publish_date names one day and stores two: the API writes target_publish_start_date = the day you sent and target_publish_end_date = that day + 1, and the manager may post on either. 46 512 of 46 571 production video rows carry exactly that shape. Nothing about the behaviour changed — the descriptions did, because callers were reading the field as a fixed date.

  • The end day is derived and cannot be chosen at configuration time. Two live REST integrations had been sending a target_publish_end_date on PUT /bundles/{id}/videos/{position}; the field does not exist there, so it was dropped in silence and they received a window they never asked for, behind a 200. It is now refused with 400 UNKNOWN_FIELD whose message is the fix, and the same applies to a target_publish_end_date column in a CSV import (CSV_PARSE_ERROR). PATCH /bundles/{id}/videos/{position} is unchanged and remains the only endpoint that accepts an explicit target_publish_start_date / target_publish_end_date pair.
  • Every video response now returns the window explicitly, as target_publish_start_date and target_publish_end_date, next to the existing target_publish_date (which is an alias of the start). Nothing was renamed or removed.
  • The CSV column reference was wrong. It listed the date column as target_publish_date; the header the importer actually requires is target_publish_start_date, as the downloadable templates always had it.

auto_publish: top-level on batch, per-video on a single slot

The flag exists in two places with different scopes, and the published schema had blurred them: the batch item schema inherited auto_publish from the single-slot schema through an object spread, so the spec advertised a per-item flag the batch endpoint has never accepted. An agent read the spec, sent videos[0..7].auto_publish, and got a bare UNKNOWN_FIELD that named neither the rule nor the fix.

The semantics are untouched — there is one publish attempt per call, never one per video. What changed:

  • The batch item schema no longer declares auto_publish.
  • Both descriptions state the scope explicitly.
  • The rejection now says: "auto_publish is a top-level field on this endpoint and applies to every video in the call — move it out of videos[]."

These "real field, wrong place" refusals carry details.hints and, unlike the did-you-mean refusal, apply to every caller regardless of API_STRICT_UNKNOWN_FIELDS. The gate exists to stop a typo costing an integration its 200; here the silence was the bug. See Errors → Unknown fields.

Published tokportal-mcp 1.14.0 (91 tools, same catalogue, no signature change). No pricing changed in this release.

2026-08-22

MCP 1.13.1: the spec stops promising behaviour the code no longer has

A follow-up review of 1.13.0 found that the source of truth — the OpenAPI operation descriptions, which become openapi.json, the API reference, llms-full.txt, the npm package and the Postman collection — still described the pre-1.13.0 behaviour in a handful of places. 1.13.1 is documentation and spec fidelity: no endpoint behaviour changed except one Wave B fix that had never reached the wire (below), and no pricing changed.

  • configureBundleVideo really returns ignored_fields now. 1.13.0 computed the list of platform-inapplicable fields and then threw it away before answering, so the documented behaviour was not the deployed one. PUT /bundles/{id}/videos/{position} now returns ignored_fields and ignored_fields_note next to platform, and PUT /bundles/{id}/videos/batch returns ignored_fields on each affected row. An empty or absent list is the only proof that everything you sent was stored.
  • Report exports no longer advertise silent coercion. createAnalyticsReport and exportAnalyticsReportHtml described malformed dates as "silently treated as no filter" and an unknown template as "silently becomes executive". Both have validated since 1.13.0; the descriptions now say so, publish the template enum, and point at applied (JSON) and the X-TokPortal-Report-* headers (HTML).
  • uploadImage no longer claims the stored extension comes from filename. The declared content_type names the object; filename is only the fallback. Read storage_path for the real key.
  • uploadImageDirect no longer claims an unrecognised purpose is accepted. An explicitly wrong value is refused with INVALID_FIELD for MCP callers and reported in meta.ignored_fields for REST callers; an omitted purpose still means carousel.
  • GET /credit-costs stops advertising legacy warming. niche_warming and deep_warming keep their keys so existing integrations keep parsing, but their description and note now read LEGACY — not offered, and the examples.account_with_10_videos_and_warming quote is 3 Advanced Niche Warming targets at the workspace's effective rate instead of a niche-warming session. Advanced Niche Warming is the only warming TokPortal sells; nothing about the legacy behaviour changed.
  • Country codes are documented as they really are. The spec claimed country is a 2-letter ISO code and that USA is rejected, while GET /countries returns USA for the United States and UK for the United Kingdom. The codes that endpoint returns are the authority; US and GB are accepted aliases resolved to them. Every page that taught the opposite has been corrected.
  • Every request example in the spec now validates against the schema it illustrates. Nine operations shipped examples that their own schema rejected — target_publish_date sampled as the literal string target_publish_date, an image upload with content_type: "video/mp4", three-target warming lists with one item, and a batch item schema (allOf over a closed object) that no request could ever satisfy. Copy-pasteable examples are how an agent learns a shape, so a failing one is a bug.

Published tokportal-mcp 1.13.1 (91 tools, same catalogue, no signature change). Upgrading is recommended but not required.

MCP 1.13.0: truthful annotations, honest descriptions, and errors instead of silent drops

Two audits of the public API and the MCP surface — one on schema fidelity, one on what an agent can actually understand from the tool catalogue — found requests that the API answered 2xx, charged for, and did not honour. This release fixes the metadata first, then the behaviour.

Annotations now describe the real risk. Published tokportal-mcp 1.13.0 (91 tools, same catalogue). Until 1.12.2 destructiveHint was derived from the verb in the operation name: it flagged 14 tools, including reversible ones like tokportal_reset_bundle_video and tokportal_unschedule_bundle_video, while every credit-spending call went unflagged. It now means one thing — this call spends credits or releases a manager payout irreversibly — plus the genuine cancel / delete / revoke operations. That is 23 tools; the twelve credit-spending operations that used to slip through now carry it. readOnlyHint stays 41, idempotentHint 47, openWorldHint 16. If your host auto-approves anything not marked destructiveHint, re-check that policy. Full list on MCP → Tool annotations.

69 of 91 tool descriptions rewritten, and the server instructions now lead with the traps rather than the branding. Rules that the JSON Schema cannot express now sit on the field they constrain: the publish lead time (today + 3 days while the account is still being created, today + 1 once it is delivered or on an existing account), the ~72-hour auto-finalize window that applies regardless of auto_finalize_videos, one bundle = one account, max 3 videos per day per bundle, storage_path vs public_url, instagram_content_type on Instagram slots, and tiktok_sound_url on TikTok carousels.

Advanced Niche Warming fails loudly instead of under-delivering. wants_advanced_warming: true is mandatory whenever advanced_warming_terms or advanced_warming_terms_count is sent, and is never inferred. Three new 400s on POST /bundles and POST /bundles/bulk:

  • ADVANCED_WARMING_FLAG_REQUIRED — targets sent without the flag (absent or explicitly false). Previously the targets were dropped and the bundle was charged anyway.
  • ADVANCED_WARMING_TERMS_REJECTED — a target would have been removed by cleanup (case-insensitive duplicate, or outside 2-50 characters); details.dropped_terms lists them. TokPortal will not deliver or bill fewer targets than you asked for.
  • ADVANCED_WARMING_COUNT_MISMATCH — the term list and the count state different quantities; neither silently wins.

The live case behind this: 6 terms containing 3 case-duplicates plus advanced_warming_terms_count: 9 produced 3 targets and 15 credits instead of 45. That request is now refused.

Advanced Niche Warming is the only warming TokPortal sells. wants_niche_warming, wants_deep_warming and niche_warming_instructions are marked deprecated and now carry a single sentence — kept only for accounts being re-created after a refund. Their behaviour is byte-for-byte unchanged (new deep-warming orders are still rejected with DEEP_WARMING_DEPRECATED), but they are gone from every description, example and walkthrough.

Unknown fields are reported, and refused where dropping them destroys data. A misspelled biograhpy used to be stripped during validation; because PUT /bundles/{id}/account is a full replace, the stored biography was wiped to null and the call still returned 200. The six full-replace endpoints (POST /bundles, POST /bundles/bulk, PUT /bundles/{id}/account, PUT /bundles/{id}/videos/{position}, PUT /bundles/{id}/videos/batch, PUT /bundles/{id}/warming-terms) now answer 400 UNKNOWN_FIELD with a did-you-mean. Everywhere else the request runs and the names come back in meta.ignored_fields. The refusal is gated by API_STRICT_UNKNOWN_FIELDS, currently set so that MCP callers get the 400 and REST callers get meta.ignored_fields — treat anything appearing there as a bug in your integration. Over MCP, unrecognised top-level arguments are also no longer turned into query parameters. See Errors → Unknown fields.

Analytics report exports validate instead of coercing. POST /analytics/export/reports and its HTML twin ran no validation at all. The template enum is now published (executive, agency, creator, minimal, growth, board) and an unknown value is rejected rather than silently becoming executive. Dates must be YYYY-MM-DD (a full ISO timestamp is truncated to its date); an unparseable date used to be treated as no filter and quietly widened the report to the account's entire history, and an inverted window was accepted. title, brandName and query now carry their real stored limits (120 / 80 / 120). The settings actually applied come back as applied on the JSON response and as X-TokPortal-Report-* headers on the HTML export, so white-labelling can be verified instead of assumed.

Other fidelity fixes.

  • Image uploads: the required content_type finally names the stored object (it picks the extension; the filename is only a fallback), and the response returns storage_path and bucket. Use storage_path — not public_url — for carousel_images and profile_picture_url.
  • POST /upload/image/direct: an explicitly invalid purpose is refused or reported instead of landing in the carousel bucket. An omitted purpose still defaults to carousel.
  • link_in_bio on a non-Instagram bundle is reported in the response's _warnings array; platform-inapplicable video fields come back as ignored_fields.
  • Analytics refresh: includeComments without force is refused for strict callers or reported in applied. Comment pulse echoes applied_filters and says when post overrode the dashboard filters.
  • Removed: platform on POST /videos/{id}/ad-code-request. It was never read, and was already being stripped, so callers still sending it are unaffected.
  • Partial success over MCP: tokportal_batch_configure_bundle_videos and tokportal_import_bundle_videos_csv are now marked isError only when zero items succeeded while errors are present. Partial and full success are unchanged, and the REST status code did not change.

No pricing changed in this release.

2026-08-18

Distribution: MCP 1.12.1 annotations, official registry, GitHub org, PyPI/Go, plugins, docs matrix

  • Published tokportal-mcp 1.12.2: openWorldHint and destructiveHint are now precise instead of conservative — 16 tools that reach a public platform are flagged openWorldHint: true, and 14 tools whose effect cannot be undone (non-refundable credit debits, publishing, the credential reveal, payout-releasing finalizes) are flagged destructiveHint: true. Tools also carry securitySchemes and an OAuth scope (mcp / mcp:read). On assistant surfaces whose policy forbids handling authentication secrets, tokportal_reveal_account_credentials and tokportal_retrieve_account_verification_code are withheld and answer with an explanation pointing to the dashboard.
  • Published tokportal-mcp 1.12.1: every tool now carries MCP annotations (title, readOnlyHint on all GET tools, destructiveHint on unpublish/cancel/delete/reset/unschedule/revoke, idempotentHint on GET/PUT/DELETE, openWorldHint: false). The remote server at https://app.tokportal.com/api/ext/mcp serves the same annotations. Hosts that honor them can auto-approve reads and prompt before destructive calls. See MCP → Tool annotations.
  • Listed the server on the official MCP Registry as com.tokportal/mcp (synced to the VS Code / GitHub MCP registry); submitted to the Docker MCP Catalog, the Cline Marketplace and awesome-mcp-servers; published a Gemini CLI extension. Status is tracked on MCP → Registry listings.
  • Opened the tokportal GitHub organization with the public sources of tokportal-mcp, @tokportal/node, @tokportal/cli, the Python SDK (pip install tokportal, PyPI), the Go SDK (go get github.com/tokportal/tokportal-go), quickstart examples, agent skills, a Cursor plugin and a Claude Code plugin marketplace (claude plugin marketplace add tokportal/claude-plugin).
  • Docs: added the Agents × jobs matrix — 25 agent hosts (Claude, Claude Code, ChatGPT, Codex, Cursor, Windsurf, Cline, OpenClaw, Hermes Agent, Gemini CLI, Goose, Perplexity, Copilot Studio, Factory Droid, n8n, Make, Zapier, Pipedream, Dify, Flowise, Langflow, CrewAI, LangChain, Vercel AI SDK, Mastra) × 12 jobs, each with the host's exact connect snippet, the tokportal_* tool sequence, REST equivalent, parameters, example configs and FAQ. Added a developer blog with RSS. All new pages are in sitemap.xml, llms.txt and the IndexNow ping.
  • Docs: llms.txt now states Instagram's documented API publishing limit as 100 posts per 24-hour moving period (previously 25).

Pricing documentation clarified (no API change)

  • Credits & Pricing now lists every billable operation, including the ones that were only documented on their own endpoint page: story repost link (1 credit), instant repost as story (1 credit), ad code (7 credits), and account edit request (8 credits).
  • Added an explicit grandfathering table. Grandfathering is decided per saved account from its created_at against the immutable production cutover, not per workspace: accounts delivered before the cutover are never charged TokPortal Coverage and keep the prior 0-credit credential reveal, permanently. The cutover value returned by GET /credit-costs is documented for reference, and it is still the only authority.
  • Restated that both workspace action-price grace windows closed on August 14, 2026 at 11:00 UTC. The current standard rates are 32 credits per TikTok/Instagram account setup and 5 credits per Advanced Warming target; earlier published figures (25 credits per setup, 3 or 4 credits per warming target) are historical.
  • Clarified that Advanced Niche Warming is billed per niche target, never per day — the 3-day schedule is a delivery window, not a billing unit.
  • Clarified the two different 25-credit figures: the recurring 25 credits every 30 days is TokPortal Coverage per eligible saved account. The old per-bundle "comment moderation" add-on no longer exists; comment work is billed per task at 1 credit through the Comments API, with no monthly component.
  • Corrected the GET /credits/history sample payload, which still showed a 25-credit account creation line.

2026-08-16

Daily scheduling cap (max 3 videos per day per bundle)

  • A bundle can now have at most 3 videos targeting the same target_publish_date day. The cap counts every non-cancelled video slot that already has a date on that day, whatever its status; re-configuring a video on its own current day does not count against itself.
  • Enforced on PUT .../videos/{position} (configure), PUT .../videos/batch, POST .../videos/import-csv, and PATCH .../videos/{position} date updates. A 4th video on the same day is rejected with the new VIDEOS_PER_DAY_EXCEEDED error (HTTP 400) carrying details.date, details.limit, and details.current_count. In a batch, the first 3 rows on a given day succeed and later rows fail with per-item errors.
  • The dashboard's bundle video editor and CSV import apply the same rule with an inline explanation. See Configure Videos → Daily Scheduling Cap.

Ban lifecycle visibility

  • Published tokportal-mcp 1.12.0 with the new tokportal_list_account_bans tool (91 generated operations). The remote MCP connector serves the same updated catalogue automatically.
  • Added GET /account-bans: a pollable list of validated ban reports for your delivered accounts covering the whole lifecycle — appeal_pending (platform appeal filed, account unavailable but not yet banned), appeal_accepted, appeal_refused, no_appeal_banned, and the staff commercial resolution (refund / remake / no_remake with a machine-readable reason_code such as tos_ban, and refund_credits). Supports status, resolution (including pending), account_id, since (an updated_at polling watermark), and include_screenshots for a signed 7-day evidence URL. See Bans & Appeals.
  • Added the account.ban_appeal.submitted webhook event, emitted the moment the manager files a platform appeal. This is the first push signal that an account is unavailable — previously nothing fired until the appeal resolved days later.
  • Added the account.ban_resolution.decided webhook event, emitted when staff decides the commercial outcome of a confirmed ban (refund, remake, or no_remake + reason_code + refund_credits).
  • Added a banned filter to GET /accounts (true = only banned accounts, false = only healthy ones).
  • Added a ban_appeal block to GET /accounts and GET /accounts/{id} responses: the latest validated ban report for the account ({ id, status, reported_at, decided_at, resolution }), so list polling alone now surfaces pending appeals and resolutions.
  • Existing integrations are unaffected: account.banned, account.ban_appeal.resolved, bundle.cancelled, and the existing ban field keep their exact shapes; the new events only reach endpoints that subscribe to them.

2026-08-11

  • Prepared the compatible client releases @tokportal/node 0.1.1, @tokportal/cli 0.1.2, and tokportal-mcp 1.11.0. Use these versions, or a later release, for the Coverage convenience methods described below.
  • Clarified the credential reveal contract for existing integrations. A no-body POST /accounts/{id}/reveal-credentials or POST /accounts/{id}/verification-code is a safe policy preview: it returns 428 CREDENTIALS_ACKNOWLEDGMENT_REQUIRED and does not reveal credentials, debit credits, or detach the account. The OpenAPI request body is now optional for this preview, while the accepted body requires the literal value acknowledge_support_forfeit: true and the exact server-provided policy_version.
  • Added ergonomic TokPortal Coverage read, pause, and exact-quote reactivation methods to the maintained Node, Python, and Go SDK sources. Added accepted credential-reveal and verification-code bodies while preserving the older no-body SDK calls as safe preview calls.
  • Updated generated CLI, local MCP, and remote MCP schemas so agents can perform the no-body preview before asking a human for consent. Generated accepted examples now use true and the server-returned policy version placeholder. Secret-returning tools reject idempotency keys before execution so credentials and codes are never stored in a replay ledger. Remote MCP errors now include the same request ID, retry delay, and rate-limit diagnostics as local MCP.
  • Added explicit recovery guidance for Coverage, credential policy, idempotency, and legacy API-key errors. managed_subscription: null is no longer described as universal proof of grandfathering because ineligible platforms and missing pre-delivery records can also have no subscription.
  • Clarified durable idempotency recovery. Completed responses replay for 24 hours, while an uncertain processing claim never expires automatically into a second financial execution. Integrations must stop on a persistent IDEMPOTENCY_KEY_IN_PROGRESS and request support/operator reconciliation instead of changing keys.
  • Existing metadata, bundle, and analytics calls remain compatible. Existing no-body credential calls become safe previews rather than silent reveals. Integrations that reveal credentials must add a human confirmation step and resend the current policy version.
  • TokPortal will proactively contact workspaces with active API-key records in the retired legacy credential format. Those old hashes cannot be identified safely during a request without an expensive pre-authentication scan, so integrations must not expect a dedicated runtime error code. Generate a current key, deploy it, verify successful requests, and only then revoke the old key. If an old key is no longer recognized, the API returns the ordinary AUTH_INVALID_KEY response.

Integration migration checklist

  1. Keep all existing read and bundle calls unchanged.
  2. Read managed_subscription from account details or call the dedicated Coverage endpoint. Treat lapsed and cancelled as blocked states that need an exact-quote reactivation; treat terminal states as non-recoverable.
  3. For reactivation, copy the latest quote fields exactly. Show the amount even when it is 0, send an Idempotency-Key, and fetch a new quote on QUOTE_CHANGED.
  4. For credentials or verification codes, first call without a body. Display the complete 428 disclosure to the account owner. Only after confirmation, send acknowledge_support_forfeit: true and the exact returned policy_version, without an idempotency key. On CREDENTIAL_REVEAL_QUOTE_CHANGED, obtain a new preview and fresh confirmation.
  5. Log X-TokPortal-Request-ID, the machine error code, and your own account or job correlation ID. Never log credentials, verification codes, API keys, or accepted disclosure payloads containing secret results.
  6. Upgrade to the package release whose changelog contains this entry before relying on the convenience methods. Until that release is published, use the raw HTTP endpoints or a pinned reviewed SDK source commit.

Managed pricing production cutover

  • Added cohort-aware pricing from the actual production migration cutover. Workspaces without a paid credit acquisition before that timestamp move immediately to 32 credits for account setup, 5 per Advanced Niche Warming target, and 150 for a first credential or verification-code reveal. Workspaces with a paid acquisition before the cutover keep 25, 3, and 0 credits until August 14, 2026 at 11:00 UTC, then move to 32, 5, and 150.
  • Added TokPortal Coverage for eligible TikTok and Instagram saved accounts. The production migration records the authoritative account cutoff. Every eligible saved account that already exists at that instant remains permanently grandfathered and returns managed_subscription: null; ineligible platforms or accounts without a Coverage record can also return null.
  • Checkout charges only the workspace-effective setup price, 25 credits during a pre-cutover paying workspace's legacy window or 32 credits after transition. No Coverage period is charged at checkout. For a non-grandfathered saved account created after the migration cutover, Coverage starts at account review submission even when that workspace keeps legacy action prices until August 14. The first 30 days are included, and the first automatic 25-credit debit on day 30 covers the next 30 days. Every successful renewal appears in credit transaction history. Coverage is unique per saved account, independent from bundle lifecycle, and never refunded.
  • Added failed-payment and manual-pause task blocking, arrears tracking, exact reactivation pricing, and atomic task rescheduling after payment. Completed or cancelled bundles do not end Coverage. Bundles with no newly scheduled or posted video for 60 days are archived from the manager workspace without changing Coverage.
  • Corrected reactivation pricing so an account resumed inside its already paid or included period costs 0 credits. Later reactivation pays only the exact unpaid periods, preserves the original billing anchor, and rejects stale price confirmations without a debit.
  • Added confirmed-ban handling: a confirmed banned-account refund or eligible credit restoration changes Coverage to terminal ended_ban, prevents future renewals, and emits subscription.ended alongside the applicable credit event.
  • The workspace-effective price for credential reveal and verification-code retrieval is 150 credits from the production cutover for workspaces without a prior paid credit acquisition. A pre-cutover paying workspace remains at 0 credits until August 14 at 11:00 UTC. An admin-approved Account Owning Fee agreement still replaces the one-time charge. The post-transition reveal permanently detaches the account from TokPortal. Versioned policy acceptance, debit and shutdown are atomic. Historical reveals remain grandfathered.
  • Account Owning Fee activation now requires admin approval after the 25-account and 25-percent-revealed threshold. Pending workspaces retain the 150-credit reveal option. After approval, reveal activates or uses the stored agreement rate instead. Existing legacy agreements remain at $10 per account every 30 days; new approvals use $15.
  • Confirmed eligible ban restoration covers account setup, warming and unused video-slot credits when Coverage was active at the ban occurrence, the account was never revealed, and the claim is opened within 15 days under the Ban and Replacement Policy. Restored credits expire after 60 days; Coverage periods are never refunded.
  • Added the stable technical managed_subscription field to GET /accounts/{id} and dedicated TokPortal Coverage read, pause, and reactivation endpoints across the public API, generated SDKs, CLI, local MCP and remote MCP.
  • Added subscription.renewed, subscription.lapsed, subscription.cancelled, subscription.reactivated, subscription.ended, bundle.archived, account.revealed and credits.restored webhook events.

2026-07-16

  • Clarified the account and video review lifecycle: an account listing or video listing left in in_review without user review is automatically moved to finalized after 72 hours.

2026-07-12

  • Ban visibility end-to-end. When an account ban is validated (manager report with no appeal, refused platform appeal, or staff confirmation), TokPortal now cancels every active bundle/order on the account and pushes the signal everywhere:
    • New bundle.cancelled webhook event — fired for each cancelled bundle with reason, cancelled_at, account_banned, saved_account_id, and username. See Webhooks.
    • Documented the existing account.banned and account.ban_appeal.resolved webhook events (emitted since the CM ban-report flow shipped).
    • GET /accounts and GET /accounts/:id now return banned and a ban object (reason, banned_at, plus appeal_status and a signed screenshot_url on the detail response). Banned accounts stay visible.
    • GET /bundles and GET /bundles/:id now return cancelled_at and cancelled_reason.
    • 409 BUNDLE_INVALID_STATUS on publish calls now includes cancelled_at / cancelled_reason when the bundle is cancelled, so the error itself tells you why.
    • Documented the cancelled bundle status in the bundle lifecycle.

2026-07-10

  • Advanced Niche Warming was introduced at 3 credits per niche target, with 3-30 targets in multiples of 3. The August 2026 transition updates the rate to 5 credits. New bundle fields wants_advanced_warming + advanced_warming_terms / advanced_warming_terms_count are available on POST /bundles and POST /bundles/bulk. See the Advanced Niche Warming guide.
  • Deferred target configuration: buy the target count at creation (advanced_warming_terms_count) and write the targets afterwards via PUT /bundles/:id/warming-terms with advanced_warming_terms (one-shot; must match the purchased count exactly). Warming starts at configuration when the account/order is already active, else at submission/accept. Count-only purchases never configured within 14 days are auto-cancelled and fully refunded.
  • Warming task spec: each recording opens on the account profile (handle visible), then the manager searches the target, watches videos from the results, engages with them (likes/saves) and leaves a comment. Every recording is verified before it counts.
  • New endpoints: PUT /bundles/:id/warming-terms, POST /accounts/:id/rewarm, GET /accounts/:id/warming-sessions, GET /warming-sessions/:id, POST /warming/generate-terms (free AI target generation).
  • New webhooks: warming.session_started, warming.term_verified, warming.session_completed. Bundle payloads expose advanced_warming_terms_count; account payloads gain an advanced_warming summary (with terms_configured).
  • New error codes: ADVANCED_WARMING_PLATFORM, ADVANCED_WARMING_TERMS, WARMING_TERMS_ALREADY_SET, WARMING_SESSION_NOT_FOUND, REWARM_NO_ACTIVE_ORDER, REWARM_ALREADY_ACTIVE, TERM_GENERATION_FAILED, AI_UNAVAILABLE.
  • Deep warming is discontinued: wants_deep_warming: true is now rejected with DEEP_WARMING_DEPRECATED (in-flight orders keep working). Niche warming is review-based and will be deprecated in favor of Advanced Niche Warming.

2026-06-19

  • Added the account.remade webhook event, emitted when an account is remade after a ban or loss. Payload includes bundle_id, external_ref, old_username, new_username, old_account_id, reason, mode, remade_count, and remade_at.
  • Added a remade object to bundle responses (GET /bundles and GET /bundles/:id) with was_remade, remade_count, old_username, and remade_at.
  • Added bundle_id and external_ref to the account detail response (GET /accounts/:id) — stable anchors for correlating an account across remakes (the saved account id itself changes on a remake).
  • Added saved_account_id to GET /bundles/:id (top-level and inside account) and to the account.* webhook payloads (populated from account.in_review onward) — the clean way to obtain the real/new saved-account id for a bundle, including after a remake. Documented the account-listing vs saved-account distinction.

2026-05-25

  • Added first-class OpenAPI assets at /openapi.json and /openapi.yaml.
  • Added llms.txt and llms-full.txt for LLM ingestion.
  • Added ai-context.json and developer-ecosystem.json for structured AI and developer ecosystem ingestion.
  • Documented the canonical API key format: sk_ followed by 64 lowercase hex characters, stored as SHA-256, shown once at creation.
  • Added public Node / TypeScript SDK package: @tokportal/node.
  • Added public CLI package: @tokportal/cli.
  • Added published public MCP package: tokportal-mcp.
  • Deferred Python, Go, Ruby, Java, PHP, .NET, and Rust SDKs until their registry or repository release path is ready.
  • Added package distribution metadata and a public ecosystem manifest with release status for SDK, CLI, and MCP surfaces.
  • Added structured SDK error handling docs for Node, including request IDs and retryability helpers.
  • Added SDK idempotency examples.
  • Added X-TokPortal-Client identification headers across generated SDKs, CLI, and MCP server.
  • Added public API version and stability response headers: X-TokPortal-API-Version: 2026-05-25 and X-TokPortal-API-Stability: stable.
  • Added X-TokPortal-Request-ID response headers across public API responses and SDK error parsing for support/debug correlation.
  • Added request ID persistence in API logs and per-key usage inspection in the Developer Portal.
  • Added API key environment tags, read/write scopes, optional expiry dates, and scope-aware auth errors without changing existing keys.
  • Added API key metadata editing and audit trail events for creation, updates, rotation, rotation replacements, and revocation.
  • Added X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After headers for authenticated public API requests.
  • Added SDK error metadata for rate limiting and backoff: Node retryAfterSeconds and rateLimit.
  • Added Node webhook signature helper docs plus manual HMAC verification examples.
  • Added CLI and MCP error diagnostics with request_id, retry_after_seconds, and rate_limit metadata.
  • Added the Versioning & Stability docs page with backwards compatibility and deprecation policy.
  • Added public webhook endpoint registry at /webhooks with emitted bundle, account, video, and test events, delivery logs, and test delivery support.
  • Added the public webhook event catalog at /webhooks/events with event availability, payload schemas, example payloads, delivery headers, and signature metadata.
  • Added manual webhook delivery retry at /webhooks/{id}/deliveries/{delivery_id}/retry, preserving the event ID and recording a new signed attempt.
  • Added persisted Idempotency-Key handling for mutating API requests, including replay detection and conflict errors.
  • Expanded OpenAPI/SDK coverage for Analytics v2 contract/export/report endpoints and media upload helpers, including direct upload and image import from URL.
  • Expanded OpenAPI/SDK coverage for bundle updates, account/video review lifecycle actions, video download fixes, analytics refresh checks, raw analytics snapshots, and standalone HTML report export.
  • Added generated OpenAPI x-codeSamples for every operation across curl, Node, Python, and Go.
  • Added a generated /api-reference docs page from OpenAPI so every operation and code sample is searchable and included in llms-full.txt.
  • Kept legacy API key validation compatible while new keys use the canonical sk_ format.