Source: https://developers.tokportal.com/changelog/
Markdown: https://developers.tokportal.com/changelog.md

# Changelog

## 2026-10-20

### Archive a video

New endpoint [`POST /videos/{id}/archive-request`](https://developers.tokportal.com/archive-request) hides one finalized TikTok or Instagram video from the public profile: the account manager sets it to **Only you** on TikTok or **archives** it on Instagram. The video is not deleted. It costs **1 credit**, with the same Coverage and manager rules as account edit and ad code requests, and the debit is atomic with the request.

After the manager confirms, TokPortal checks that the post is no longer public before marking it archived. Poll [`GET /videos/{id}/archive-request`](https://developers.tokportal.com/archive-request) until `archived` is `true`. `GET /credit-costs` now returns `video_archive`.

- **Dashboard**: **Archive video** in the options of a finalized video (account Videos tab) and **Archive** in the video sheet of the Calendar. Archived videos show the **Archived** status in the Videos tab, the order page and the Calendar.
- **SDKs, CLI and MCP**: `createVideoArchiveRequest` / `getVideoArchiveRequest`, `tokportal create-video-archive-request`, and the `tokportal_create_video_archive_request` / `tokportal_get_video_archive_request` tools. The create tool is annotated `destructiveHint` and `openWorldHint`.

## 2026-09-22

### Platform signals: why a posted video gets no views

TikTok silently excludes some posts from the For You feed (**Not For Feed / Not Recommended**): the post stays online, and sits at 0 views. On a large sample this single flag explained 86 % of videos stuck at zero. TokPortal now reads it — together with the AI label TikTok applies and sound restrictions (non-commercial, removed for copyright, muted on Instagram) — and surfaces it everywhere a video appears:

- **API**: every video from `GET /bundles/{id}/videos` and `GET /bundles/{id}/videos/{position}` carries `platform_signals` (`not_recommended`, `ai_label`, `sound`, `sound_reason`, `checked_at`). `null` means *not checked yet*, never *no*. `GET /videos/{id}/analytics` adds `signal_insights`. New endpoint [`GET /analytics/signals`](https://developers.tokportal.com/analytics#flagged-posts) lists every flagged post. Analytics contract version `2026-09-22.1`.
- **Webhooks**: `video.not_recommended`, `video.ai_labeled`, `video.sound_restricted`, `account.distribution_warning` (≥3 of the last 5 checked posts not recommended). Endpoints created before today must `PATCH` their `events` list to receive them — see [Platform signal events](https://developers.tokportal.com/webhooks#platform-signal-events).
- **Dashboard**: badges on the videos tab, the order progress page and Analytics (posts list, post detail, new "Distribution health" card per account), grouped in-app notifications per order per day, and an opt-in daily e-mail digest (Settings → Notifications).
- **MCP**: `tokportal_list_analytics_signals`; every video tool returns `platform_signals`.

Also new from the connected TikTok API, on Creator and above: per-post **retention curve** (share of viewers still watching at each second), **like curve**, **followers gained per post**, **new vs returning viewers** and the full **impression-source** breakdown. Instagram accounts get the official daily **reach** series and the 30-day **followers / non-followers reach split**.

Checks run about 48 hours after posting and again at 7 days, then on every analytics scrape. Signals appear progressively on videos posted before today as the backfill and the scrapes reach them.

### Remote MCP connector for Team members

An invited [Team](https://developers.tokportal.com/mcp/remote#teams) member who authorized the remote connector received a key on their own, empty account: the assistant authenticated fine and then reported 0 bundles, 0 accounts, 0 credits and no subscription. The connector now opens the **team's workspace** (the owner's data and credit pool), exactly like the dashboard and `POST /api/keys`. The consent screen names the workspace owner, the key is listed in the owner's Developer → API Keys as `<Host> (MCP connector) · member@example.com`, and the team permissions apply: **API access** to connect, **Spend credits** for Full access (Read-only is always allowed). Members who connected before this change should remove the connector in their host and authorize it again. Nothing changes for solo accounts.

## 2026-09-06

### Local MCP sandbox transport fixed in 1.15.1

`tokportal-mcp` **1.15.1** forwards the top-level `dry_run` argument as the `X-TokPortal-Dry-Run` request header. Version 1.15.0 exposed the argument in its tool catalogue but sent it as a query parameter, so a local write could execute for real. Upgrade and restart your local server before using the sandbox. The remote MCP endpoint already forwards the header.

Install with `npm install --global tokportal-mcp@1.15.1`, or set your host's `npx` arguments to `["-y", "tokportal-mcp@1.15.1"]`. See [Sandbox](https://developers.tokportal.com/sandbox) for the request contract and [MCP installation](https://developers.tokportal.com/mcp) for connection options.

The quickstart and agent guides now put simulations first, show the returned `credits_would_charge`, and request confirmation before real bundle creation, when credits are debited.

## 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)](https://developers.tokportal.com/sandbox).

### 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, but does not forward the dry-run header. Local simulations require **1.15.1 or later**; upgrade and restart the server before using them.

## 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](https://developers.tokportal.com/errors#when-the-field-is-real-but-sent-to-the-wrong-place).

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](https://developers.tokportal.com/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 `400`s 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](https://developers.tokportal.com/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](https://developers.tokportal.com/mcp#tool-annotations).
- Listed the server on the [official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=tokportal) 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](https://github.com/tokportal/gemini-cli-extension). Status is tracked on [MCP → Registry listings](https://developers.tokportal.com/mcp#registry-listings).
- Opened the [tokportal GitHub organization](https://github.com/tokportal) 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](https://developers.tokportal.com/agents) 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](https://developers.tokportal.com/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](https://developers.tokportal.com/credits) 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](https://developers.tokportal.com/configure-videos).

### 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](https://developers.tokportal.com/bans-and-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](https://developers.tokportal.com/webhooks#the-bundlecancelled-event).
  - 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](https://developers.tokportal.com/bundles#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](https://developers.tokportal.com/advanced-warming).
- **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.
