Source: https://developers.tokportal.com/publish-unpublish/
Markdown: https://developers.tokportal.com/publish-unpublish.md

# Publish & Unpublish

Control the lifecycle of a bundle by publishing, unpublishing, or adding slots after creation.

***

## Publish

Submit a configured bundle for processing. The account must be fully configured, and for `account_and_videos` bundles, at least one video must be configured.

```
POST /bundles/:id/publish
```

### Requirements

- Bundle must be in `pending_setup` status.
- Account configuration must be complete (username, visible name, biography, profile picture).
- For `account_and_videos` type: at least 1 video must be configured.
- One bundle is exactly one account: publishing submits that single account and its slots.
- If the bundle already resolves to a delivered saved account, that account must have active TokPortal Coverage or be permanently grandfathered. A new account that has not produced a saved account yet remains publishable before Coverage starts.

Publication uses a rolling capacity shared by the client workspace owner and all Team members. The capacity remains invisible during normal operation. When no publication capacity is currently available, the endpoint returns `capacity_cooldown`; retry later.

> **INFO: Minimum publish lead time**
> A video's `target_publish_date` cannot be scheduled arbitrarily close to today. The earliest allowed date is:
>
> | Bundle situation                                                          | Earliest `target_publish_date` |
> | ------------------------------------------------------------------------- | ------------------------------ |
> | The account is still being created                                        | **today + 3 days**             |
> | The account has been delivered, or the bundle runs on an existing account | **today + 1 day**              |
>
> An earlier date is rejected with `INVALID_DATE`; `details.earliest_allowed` carries the first acceptable day and `details.min_days_ahead` the lead time that was applied. Dates are compared in **UTC**. The same limit applies to rescheduling via `PATCH /bundles/:id/videos/:position` — enforced since 2026-08-26, where before only the per-day cap ran there — and to bulk paths (`PUT /bundles/:id/videos/batch`, CSV import) where offending rows fail individually while the rest succeed.
>
> `target_publish_date` is the **first day of a 2-day window**: the end day is derived as that day + 1 and cannot be chosen at configuration time. See [The publishing window](https://developers.tokportal.com/videos/configure-videos#the-publishing-window).
>
> A bundle also accepts a maximum of **3 videos per day**; a 4th video targeting the same day is rejected with `VIDEOS_PER_DAY_EXCEEDED`. Plan the calendar before publishing so the whole batch clears both limits.

> **INFO: Publishing never rejects a stale date — it moves it**
> A draft can sit in `pending_setup` until its dates reach today. Rather than making such a bundle unpublishable, `POST /bundles/:id/publish` moves every slot whose target date has already reached today or the past forward to the earliest allowed day, honouring the 3-per-day cap; slots still in the future are left alone, and the publish never fails because of it.
>
> When something moved, the response carries `adjusted_videos` — one `{ video_id, position, previous_date, new_date }` entry per moved slot — and `adjusted_videos_note`. Read them back instead of assuming the schedule you sent survived. The same guard runs on every `auto_publish` path. See [Stale dates at publish time](https://developers.tokportal.com/videos/configure-videos#stale-dates-at-publish-time).

### Example

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

### Response

```json
{
  "data": {
    "id": "bnd_abc123",
    "status": "published",
    "published_at": "2026-02-10T14:00:00Z"
  }
}
```

### Error Responses

| Status | Code                           | Description                                                                                                                                                          |
| ------ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `account_not_configured`       | Account configuration is incomplete.                                                                                                                                 |
| `400`  | `no_videos_configured`         | At least 1 video is required for `account_and_videos` bundles.                                                                                                       |
| `409`  | `invalid_status`               | Bundle is not in `pending_setup` status.                                                                                                                             |
| `409`  | `MANAGED_ACCOUNT_TASK_BLOCKED` | Coverage is inactive for the resolved saved account. Read `details.account_id` and `details.reason`, fetch its Coverage state, and reactivate only when recoverable. |
| `429`  | `capacity_cooldown`            | The workspace's rolling publication capacity is temporarily exhausted. Retry later.                                                                                  |

***

## Unpublish

Revert a published bundle back to `pending_setup`. This allows you to make further configuration changes before resubmitting.

```
POST /bundles/:id/unpublish
```

> **WARNING: warning**
> Unpublishing does **not** refund credits. The credits charged at bundle creation remain deducted.

### Requirements

- Bundle must be in `published` status.
- Bundle must not have been accepted yet.

### Example

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

### Response

```json
{
  "data": {
    "id": "bnd_abc123",
    "status": "pending_setup",
    "unpublished_at": "2026-02-10T15:30:00Z"
  }
}
```

### Error Responses

| Status | Code               | Description                                         |
| ------ | ------------------ | --------------------------------------------------- |
| `409`  | `invalid_status`   | Bundle is not in `published` status.                |
| `409`  | `already_accepted` | Bundle has been accepted and cannot be unpublished. |

***

## Add Video Slots

Add additional video slots to an existing bundle. Credits are debited immediately.

```
POST /bundles/:id/add-video-slots
```

### Request Body

| Field      | Type    | Required | Description                                   |
| ---------- | ------- | -------- | --------------------------------------------- |
| `quantity` | integer | Yes      | Number of video slots to add. Must be `>= 1`. |

### Requirements

- Bundle type must be `account_and_videos` or `videos_only`.
- Bundle must not be in `completed` status.
- If the bundle resolves to a saved account, that account must have active TokPortal Coverage or be permanently grandfathered.

New slots are created empty. When you configure them, their `target_publish_date` still obeys the minimum publish lead time and the maximum of 3 videos per day per bundle described above.

### Example

```bash
curl -X POST https://app.tokportal.com/api/ext/bundles/bnd_abc123/add-video-slots \
  -H "X-API-Key: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "quantity": 2
  }'
```

### Response

```json
{
  "data": {
    "id": "bnd_abc123",
    "videos_quantity": 5,
    "previous_videos_quantity": 3
  },
  "credits_charged": 4,
  "credits_remaining": 496
}
```

### Error Responses

| Status | Code                           | Description                                                                       |
| ------ | ------------------------------ | --------------------------------------------------------------------------------- |
| `400`  | `invalid_quantity`             | Quantity must be at least 1.                                                      |
| `402`  | `insufficient_credits`         | Not enough credits.                                                               |
| `409`  | `BUNDLE_TYPE_INCOMPATIBLE`     | Bundle is `account_only`. Create a `videos_only` bundle instead.                  |
| `409`  | `bundle_completed`             | Cannot add slots to a completed bundle.                                           |
| `409`  | `MANAGED_ACCOUNT_TASK_BLOCKED` | Coverage is inactive for the resolved saved account. No slot or debit is created. |

***

## Add Edit Slots

Add editing slots to an existing bundle. Each video can have at most 1 edit slot. Credits are debited immediately.

```
POST /bundles/:id/add-edit-slots
```

### Request Body

| Field      | Type    | Required | Description                                  |
| ---------- | ------- | -------- | -------------------------------------------- |
| `quantity` | integer | Yes      | Number of edit slots to add. Must be `>= 1`. |

### Requirements

- Bundle type must be `account_and_videos` or `videos_only`.
- Total `edits_quantity` must not exceed `videos_quantity` (max 1 edit per video).
- Bundle must not be in `completed` status.
- If the bundle resolves to a saved account, that account must have active TokPortal Coverage or be permanently grandfathered.

### Example

```bash
curl -X POST https://app.tokportal.com/api/ext/bundles/bnd_abc123/add-edit-slots \
  -H "X-API-Key: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "quantity": 1
  }'
```

### Response

```json
{
  "data": {
    "id": "bnd_abc123",
    "edits_quantity": 3,
    "previous_edits_quantity": 2,
    "videos_quantity": 5
  },
  "credits_charged": 3,
  "credits_remaining": 497
}
```

### Error Responses

| Status | Code                           | Description                                                                       |
| ------ | ------------------------------ | --------------------------------------------------------------------------------- |
| `400`  | `invalid_bundle_type`          | Bundle type does not support videos/edits.                                        |
| `400`  | `edits_exceed_videos`          | Adding these slots would exceed the 1-edit-per-video limit.                       |
| `400`  | `invalid_quantity`             | Quantity must be at least 1.                                                      |
| `402`  | `insufficient_credits`         | Not enough credits.                                                               |
| `409`  | `bundle_completed`             | Cannot add slots to a completed bundle.                                           |
| `409`  | `MANAGED_ACCOUNT_TASK_BLOCKED` | Coverage is inactive for the resolved saved account. No slot or debit is created. |
