Source: https://developers.tokportal.com/comments-overview/
Markdown: https://developers.tokportal.com/comments-overview.md

# Comments

Schedule comments from your **delivered TokPortal accounts** on **third-party social videos**. Comments are posted by the account manager assigned to each account, then auto-verified by scraping the target video's comment list.

Each task is either a **video target** (post a comment under a video) or a **comment target** (reply to a specific comment under a video — TikTok only). Set this with `target_type`; see [Create → Target types](https://developers.tokportal.com/create-comments#target-types).

```
POST   /api/ext/comments              Create one or many comment tasks
GET    /api/ext/comments              List your comment tasks (paginated)
GET    /api/ext/comments/{id}         Read one task with full state
DELETE /api/ext/comments/{id}         Cancel a pending task (exact stored-cost refund)
POST   /api/ext/comments/{id}/approve   Approve a manually-confirmed task
POST   /api/ext/comments/{id}/dispute   Dispute a manually-confirmed task
GET    /api/ext/comments/{id}/verifications  Verbose verifier timeline
```

## Pricing

**1 credit per new comment task.** In a batch, TokPortal validates every row first. The debit for accepted rows and all accepted task inserts then commit in one database transaction. Rejected rows are never charged. This prevents a task from existing without its matching debit, or a debit from existing without its matching task.

The task's exact stored cost is refunded atomically if:

- you cancel a `pending` task via `DELETE /api/ext/comments/{id}`
- the 72-hour hard deadline expires before the manager posts (auto-cancellation)

Current tasks refund 1 credit. Historical tasks can have a different stored cost, so always read `credits_refunded` from the response instead of assuming an amount.

The CM payout is paid out of TokPortal funds — it does **not** affect your credit balance.

## TokPortal Coverage requirement

The saved account must either have active TokPortal Coverage or be permanently grandfathered. It must also belong to the caller and have an assigned account manager. A single-task request that cannot execute returns `MANAGED_ACCOUNT_TASK_BLOCKED` (409). A batch keeps partial-success semantics and places the stable account or Coverage reason in `rejected[*].reason`.

If Coverage later pauses or lapses, the task remains visible to the client but is removed from the manager's executable queue. Its durable workflow status is preserved. Read `execution_blocked`, `execution_blocked_at`, and `execution_block_reason`, then fetch `GET /api/ext/accounts/{saved_account_id}/managed-subscription` for the authoritative state and reactivation quote. Cancellation of a still-`pending` task remains available while Coverage is paused.

## Supported platforms

| Platform  | Comment posting | Reply-to-comment target | Auto-verification | Comment max length |
| --------- | --------------- | ----------------------- | ----------------- | ------------------ |
| TikTok    | ✅               | ✅                       | ✅                 | 150 chars          |
| Instagram | ✅               | ❌                       | ✅                 | 2,200 chars        |
| YouTube   | ❌ (coming)      | ❌                       | ❌                 | —                  |

Auto-verification scrapes up to 100 of the target video's most recent comments and matches them by author handle + text similarity (Sørensen-Dice ≥ 0.95). For a **comment target**, the verifier additionally pulls replies and matches the manager's reply the same way (by handle + text) — it does not require the reply to sit under a specific parent comment.

## Lifecycle

A task moves through these states. Statuses surface verbatim in `GET /api/ext/comments/{id}` so your code can branch on them.

| Status                      | Meaning                                                                                                                                                           | Visible in API           |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `pending`                   | Awaiting the account manager to post the comment. Hard deadline: 72h.                                                                                             | ✅                        |
| `submitted`                 | Manager declared they posted. Auto-verifier ran with a 30s grace window — still not matched. The CM should either retry (if a propagation lag) or manual-confirm. | ✅                        |
| `verified_auto`             | Auto-verifier matched the comment on the target video. Payout locked in 24h (auto-finalize).                                                                      | ✅                        |
| `manually_confirmed`        | Auto-verification couldn't find the comment; manager swore they posted. **You** have **72 hours** to approve or dispute. After that, payout auto-locks.           | ✅                        |
| `pending_corrections`       | The manager is fixing something **you** disputed. Hidden if it was an auto-detected issue (the manager is fixing it before you ever see it).                      | ✅ (only client-disputed) |
| `finalized`                 | Payout was locked, manager wallet credited. Terminal state.                                                                                                       | ✅                        |
| `cancelled`                 | Cancelled by you (`DELETE`) or by the 72h expiry sweep. Credit refunded.                                                                                          | ✅                        |
| `rejected` / `needs_review` | Internal — should never appear via the API.                                                                                                                       | hidden                   |

### Lifecycle diagram (happy path)

```
   create
     │
     ▼
  pending  ─── manager posts ───► submitted
                                     │
                ┌─── verifier matches ▼ ────┐
                │                            │
                ▼                            ▼
          verified_auto                manually_confirmed
                │                            │
                │ (24h auto-finalize)        │ you approve  ───► finalized
                ▼                            │ you dispute  ───► pending_corrections
            finalized                        │ 72h pass     ───► finalized (auto)
                                             ▼
                                       ─ pending_corrections
                                       │ manager fixes + retries verify
                                       ▼
                                   verified_auto / manually_confirmed
```

## Error handling

All endpoints return a uniform error envelope:

```json
{
  "error": {
    "code": "COMMENT_PLATFORM_MISMATCH",
    "message": "The video URL's platform does not match the account's platform...",
    "details": { "account_platform": "tiktok", "url_platform": "instagram" }
  }
}
```

### Comment-specific error codes

| Code                                  | HTTP | Trigger                                                                                                                                                               |
| ------------------------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `COMMENT_TASK_NOT_FOUND`              | 404  | Task ID doesn't exist or doesn't belong to your account.                                                                                                              |
| `COMMENT_TASK_NOT_OWNED`              | 403  | Task exists but belongs to another user.                                                                                                                              |
| `COMMENT_INVALID_STATUS`              | 409  | Action not allowed in current status (e.g. cancel a finalized task).                                                                                                  |
| `COMMENT_ACCOUNT_NOT_MANAGED`         | 409  | The target account has no `current_cm_id`. Wait for an order to be in progress.                                                                                       |
| `COMMENT_PLATFORM_MISMATCH`           | 400  | Target platform ≠ account platform.                                                                                                                                   |
| `COMMENT_INVALID_VIDEO_URL`           | 400  | Could not parse the video URL.                                                                                                                                        |
| `COMMENT_INVALID_COMMENT_URL`         | 400  | `target_comment_url` is not a recognizable TikTok comment link.                                                                                                       |
| `COMMENT_NOT_A_COMMENT_URL`           | 400  | The link resolved to a video, not a specific comment.                                                                                                                 |
| `COMMENT_TARGET_PLATFORM_UNSUPPORTED` | 400  | Comment targets (replies) are TikTok-only.                                                                                                                            |
| `COMMENT_TEXT_TOO_LONG`               | 400  | `comment_text` exceeds the platform's character limit.                                                                                                                |
| `COMMENT_TEXT_EMPTY`                  | 400  | `comment_text` is blank.                                                                                                                                              |
| `COMMENT_DISPUTE_REASON_REQUIRED`     | 400  | Dispute call missing or short `reason` field.                                                                                                                         |
| `INSUFFICIENT_CREDITS`                | 402  | Not enough credits for the requested batch.                                                                                                                           |
| `MANAGED_ACCOUNT_TASK_BLOCKED`        | 409  | Coverage is inactive or the account is detached, banned, missing Coverage, or otherwise unavailable. Read `details.reason`, then fetch the account Coverage endpoint. |
| `SAVED_ACCOUNT_NOT_FOUND`             | 404  | `saved_account_id` doesn't match any of your accounts.                                                                                                                |
| `SAVED_ACCOUNT_NOT_OWNED`             | 403  | Account belongs to someone else.                                                                                                                                      |

When creating a **batch**, the response always succeeds at HTTP-201 even if individual rows are rejected. Inspect `created` / `rejected` to surface per-row results.

## Batch behaviour vs single-shot

The `POST /api/ext/comments` body accepts either a single object **or** `{ "tasks": [...] }`. The two paths differ in error semantics:

- **Single object** → if the row is rejected at validation, the endpoint returns the matching `400`/`409`/`404` error code. Easier to detect failure.
- **Batch (`tasks: [...]`)** → always returns `201` with a `rejected` array even if every row fails. The batch is **partial-success** — the rows that pass are still created.

## What auto-verification actually checks

The verifier runs once, **30 seconds after the manager clicks "I posted it"** (so the platform has time to index the new comment). It then:

1. Scrapes the target video's last \~100 comments via Apify (plus replies, for comment targets).
2. Filters to comments whose author handle matches the saved account. For a **comment target**, replies are scraped too and matched the same way (handle + text) — the reply doesn't have to sit under a specific parent comment.
3. Computes Sørensen-Dice text similarity vs your `comment_text`.
4. Decides:
   - **≥ 0.95 + same handle** → `verified_auto`
   - **< 0.95 + same handle (text drifted)** → `pending_corrections`, manager is asked to edit/repost
   - **≥ 0.92 + different handle** (text matches but wrong account) → `pending_corrections`, manager asked to delete + repost from the right account
   - **nothing matches** → manager is offered the manual confirmation modal, which moves the task into `submitted` (still in flight) or `manually_confirmed` (your turn)

You can fetch the full verifier timeline via [`GET /api/ext/comments/{id}/verifications`](https://developers.tokportal.com/comments-verifications).

## Rate limits & quotas

- API rate limit: **120 req/min** per key (shared with the rest of the TokPortal API).
- Batch size cap: **200 tasks** per `POST` body.
- Hard deadline per task: **72 hours** between creation and `submitted`. Tasks that expire are auto-cancelled and credits refunded.
- While TokPortal Coverage blocks execution, withheld tasks do not consume the manager's queue or expire as late work.
