Source: https://developers.tokportal.com/video-actions/
Markdown: https://developers.tokportal.com/video-actions.md

# Video Actions

These endpoints let you take actions on individual videos within a bundle — publish, finalize, request corrections, or unschedule.

## Publish a Video

Publish a single configured video on an already-active bundle. Use this after adding new video slots to an accepted bundle, configuring them, and wanting the account manager to start working on them.

```
POST /bundles/:id/videos/:position/publish
```

The bundle must be in `published`, `published_priority`, or `accepted` status. The video must be in `configured` status.

```bash
curl -X POST https://app.tokportal.com/api/ext/bundles/{id}/videos/3/publish \
  -H "X-API-Key: sk_xxx"
```

**Response:**

```json
{
  "data": {
    "video_id": "uuid",
    "position": 3,
    "bundle_id": "uuid",
    "status": "published"
  }
}
```

### If the target date went stale

A slot can sit configured until its `target_publish_date` reaches today. Publishing does **not** reject that — it moves the slot forward to the earliest allowed day and tells you it did:

```json
{
  "data": {
    "video_id": "uuid",
    "position": 3,
    "bundle_id": "uuid",
    "status": "published",
    "date_adjusted": true,
    "original_date": "2026-08-24",
    "new_date": "2026-08-27",
    "hint": "The earliest target date in this batch was already today or in the past at publish time, so the whole batch was shifted forward by the same number of days — original pacing, gaps and order are preserved. Use PATCH /bundles/{id}/videos/{position} to reschedule."
  }
}
```

| Field           | Type    | Description                                                                                      |
| --------------- | ------- | ------------------------------------------------------------------------------------------------ |
| `date_adjusted` | boolean | Present **only** when the date was moved. Absent means the schedule you sent survived untouched. |
| `original_date` | string  | The stale date the slot carried, `YYYY-MM-DD`.                                                   |
| `new_date`      | string  | The day it was moved to, `YYYY-MM-DD`.                                                           |
| `hint`          | string  | What happened and how to change it.                                                              |

> **INFO: These four fields were documented long before they existed**
> Until 2026-08-26 the underlying procedure never returned them and the slot published on its stale date, landing in the manager's calendar as instantly-due work. They are real now.

## Publish All Configured Videos

Publish all configured videos on a bundle at once. Useful after batch-configuring multiple new slots.

```
POST /bundles/:id/videos/publish-all
```

```bash
curl -X POST https://app.tokportal.com/api/ext/bundles/{id}/videos/publish-all \
  -H "X-API-Key: sk_xxx"
```

**Response:**

```json
{
  "data": {
    "bundle_id": "uuid",
    "videos_published": 3,
    "video_ids": ["uuid1", "uuid2", "uuid3"]
  }
}
```

### Stale dates are moved forward, not rejected

If the earliest target date in the call has already reached today or the past, the whole set slides forward by the same number of days before publishing — pacing, gaps and order are preserved. If the earliest date is still in the future, nothing moves. The publish never fails because of the adjustment.

The move respects the **3 videos per day per bundle** cap: if the earliest allowed day is already full, the slot spills to the next day, and so on.

```json
{
  "data": {
    "bundle_id": "uuid",
    "videos_published": 3,
    "video_ids": ["uuid1", "uuid2", "uuid3"],
    "dates_adjusted": 2,
    "adjusted_videos": [
      { "video_id": "uuid1", "position": 1, "previous_date": "2026-08-24", "new_date": "2026-08-27" },
      { "video_id": "uuid2", "position": 2, "previous_date": "2026-08-25", "new_date": "2026-08-27" }
    ],
    "hint": "The earliest target date in this batch was already today or in the past at publish time, so the whole batch was shifted forward by the same number of days — original pacing, gaps and order are preserved. Use PATCH /bundles/{id}/videos/{position} to reschedule."
  }
}
```

| Field             | Type    | Description                                                                  |
| ----------------- | ------- | ---------------------------------------------------------------------------- |
| `dates_adjusted`  | integer | How many slots were moved. Present only when at least one was.               |
| `adjusted_videos` | array   | One entry per moved slot: `{ video_id, position, previous_date, new_date }`. |
| `hint`            | string  | What happened and how to change it.                                          |

> **WARNING: `earliest_allowed_date` no longer exists on this response**
> It was documented but never returned. `adjusted_videos` replaces it and is strictly more useful: a single "earliest allowed" day could not describe a set of slots that were spread over several days by the per-day cap. If your integration reads `earliest_allowed_date`, read `adjusted_videos[].new_date` instead.

> **TIP: Typical workflow for adding videos to an active bundle**
>
> 1. `POST /bundles/:id/add-video-slots` — Add new slots (credits debited)
> 2. `PUT /bundles/:id/videos/batch` — Configure the new videos
> 3. `POST /bundles/:id/videos/publish-all` — Publish them so the manager can start

## Finalize a Video

```
POST /bundles/:id/videos/:position/finalize
```

Marks a video as finalized, completing its lifecycle. Once finalized, no further changes can be made to the video.

A video can only be finalized when its current status is `in_review`.

> **WARNING: 72-hour review window**
> Delivered work sits in review and auto-finalizes about **72 hours (72h01) after the video listing enters `in_review`**. If you do not finalize the video or request corrections during that window, TokPortal changes its status to `finalized` on its own. Once finalized, no further corrections can be requested and the manager's payout is released.
>
> This happens **regardless of `auto_finalize_videos`** — that flag does not switch automatic finalization off. If you need control over a delivery, act inside the window with this endpoint or with [Request Corrections](https://developers.tokportal.com/video-actions/#request-corrections).

```bash
curl -X POST https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/1/finalize \
  -H "X-API-Key: sk_xxx"
```

**Response:**

```json
{
  "data": {
    "video_id": "uuid",
    "position": 1,
    "bundle_id": "bundle_abc123",
    "status": "finalized"
  }
}
```

**Error — invalid status transition:**

```json
{
  "error": {
    "code": "VIDEO_INVALID_STATUS",
    "message": "This video is not in a valid status for this action.",
    "details": {
      "current_status": "configured",
      "required": "in_review"
    }
  }
}
```

## Request Corrections

```
POST /bundles/:id/videos/:position/corrections
```

Sends the video back to the account manager with correction instructions. The video must currently be `in_review`; its status becomes `pending_corrections`.

Corrections can only be requested inside the 72-hour review window described above, which closes regardless of `auto_finalize_videos`.

| Field     | Type   | Required | Description                                                                                                                            |
| --------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `comment` | string | Yes      | Instructions describing what needs to be changed (1-2,000 characters).                                                                 |
| `fields`  | object | No       | Boolean flags for the affected areas: `video_content`, `description`, `video_editing`, and/or `sound`. Omitted fields are not flagged. |

```bash
curl -X POST https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/2/corrections \
  -H "X-API-Key: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "comment": "Sound only: replace the current sound with the official campaign track. Do not change the video or caption.",
    "fields": {
      "sound": true
    }
  }'
```

**Response:**

```json
{
  "data": {
    "video_id": "uuid",
    "position": 2,
    "bundle_id": "bundle_abc123",
    "status": "pending_corrections"
  }
}
```

## Unschedule a Video

```
POST /bundles/:id/videos/:position/unschedule
```

Reverts a video back to `configured` status (cancels scheduling). Works on `published`, `configured`, or `accepted` videos.

```bash
curl -X POST https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/3/unschedule \
  -H "X-API-Key: sk_xxx"
```

**Response:**

```json
{
  "data": {
    "video_id": "uuid",
    "position": 3,
    "bundle_id": "uuid",
    "status": "configured"
  }
}
```

**Error — invalid status:**

```json
{
  "error": {
    "code": "VIDEO_INVALID_STATUS",
    "message": "Invalid video status for this action.",
    "details": {
      "current_status": "finalized",
      "allowed": ["published", "configured", "accepted"],
      "hint": "Only published, configured, or accepted videos can be unscheduled."
    }
  }
}
```

## Reset a Video

```
POST /bundles/:id/videos/:position/reset
```

Clears all configuration from a video slot — description, URL, dates, sound, editing instructions, and all platform-specific fields — returning it to a blank state. The slot itself is preserved and can be reconfigured with `PUT /bundles/:id/videos/:position`.

Only works on `pending` or `configured` videos. If a video is `published` or `accepted`, use **unschedule** first, then reset.

```bash
curl -X POST https://app.tokportal.com/api/ext/bundles/bundle_abc123/videos/3/reset \
  -H "X-API-Key: sk_xxx"
```

**Response:**

```json
{
  "data": {
    "video_id": "uuid",
    "position": 3,
    "bundle_id": "uuid",
    "status": "configured",
    "message": "Video has been reset to blank state. You can reconfigure it with PUT /bundles/:id/videos/:position."
  }
}
```

**Error — invalid status:**

```json
{
  "error": {
    "code": "VIDEO_INVALID_STATUS",
    "details": {
      "current_status": "accepted",
      "allowed": ["pending", "configured"],
      "hint": "Only pending or configured videos can be reset. Use unschedule first if the video is published or accepted."
    }
  }
}
```

> **TIP: To fully clear a published or accepted video**
>
> 1. `POST /bundles/:id/videos/:position/unschedule` — reverts to `configured`
> 2. `POST /bundles/:id/videos/:position/reset` — clears all configuration

## Action Summary

| Action       | Endpoint                | Allowed From Status                   | Result Status                  |
| ------------ | ----------------------- | ------------------------------------- | ------------------------------ |
| Publish      | `POST .../publish`      | `configured`                          | `published`                    |
| Finalize     | `POST .../finalize`     | `in_review`                           | `finalized`                    |
| Corrections  | `POST .../corrections`  | `in_review`                           | `pending_corrections`          |
| Unschedule   | `POST .../unschedule`   | `published`, `configured`, `accepted` | `configured`                   |
| Reset        | `POST .../reset`        | `pending`, `configured`               | *(blank, same status)*         |
| Fix Download | `POST .../fix-download` | any (requires `download_issue: true`) | *(same status, issue cleared)* |

See also: [Fix Download Link](https://developers.tokportal.com/videos/fix-download-link) for full details on detecting and fixing broken links.
