Comments

Create Comments – Single & Batch

Create a single comment task or batch up to 200 from one POST. Validation rules, response shape, and error handling.

Create Comments

POST /api/ext/comments

Create one comment task or up to 200 in a single call. Each task costs 1 credit, debited up-front; rejected rows are refunded automatically.

Single task

Send a single object:

curl -X POST https://app.tokportal.com/api/ext/comments \
  -H "X-API-Key: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "saved_account_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c",
    "target_video_url": "https://www.tiktok.com/@someone/video/7000000000000000000",
    "comment_text": "Love this technique ✨"
  }'

Success (201):

{
  "data": {
    "created": [
      {
        "id": "0d8b5a3e-92c4-4111-9a7d-3e2f1a2b3c4d",
        "status": "pending",
        "platform": "tiktok",
        "saved_account_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c",
        "target_video_url": "https://www.tiktok.com/@someone/video/7000000000000000000",
        "target_author_handle": "someone",
        "comment_text": "Love this technique ✨",
        "cm_payout_amount": 0.15,
        "submitted_at": null,
        "verified_at": null,
        "manually_confirmed_at": null,
        "client_dispute_deadline_at": null,
        "finalized_at": null,
        "correction_required": null,
        "deadline_at": "2026-04-30T17:21:00Z",
        "created_at": "2026-04-27T17:21:00Z",
        "updated_at": "2026-04-27T17:21:00Z"
      }
    ],
    "rejected": []
  },
  "created_count": 1,
  "rejected_count": 0,
  "credits_charged": 1
}

On a single-task call, validation errors return a typed 4xx instead of a rejected array, so you can branch on error.code without parsing the body twice. See error codes.

Batch (up to 200)

Wrap in { "tasks": [...] }:

curl -X POST https://app.tokportal.com/api/ext/comments \
  -H "X-API-Key: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "tasks": [
      {
        "saved_account_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c",
        "target_video_url": "https://www.tiktok.com/@someone/video/7000000000000000000",
        "comment_text": "Brilliant idea"
      },
      {
        "saved_account_id": "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
        "target_video_url": "https://www.instagram.com/reel/AbCdEf12345/",
        "comment_text": "Stunning shot 🌟"
      },
      {
        "saved_account_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c",
        "target_video_url": "ftp://broken.example",
        "comment_text": "Cool"
      }
    ]
  }'

Partial-success response (201):

{
  "data": {
    "created": [
      { "id": "...", "status": "pending", "...": "..." },
      { "id": "...", "status": "pending", "...": "..." }
    ],
    "rejected": [
      {
        "reason": "invalid_video_url",
        "raw": {
          "saved_account_id": "9f3a7b2e-1c4d-4e8f-a5b6-7d9e0f1a2b3c",
          "target_video_url": "ftp://broken.example"
        }
      }
    ]
  },
  "created_count": 2,
  "rejected_count": 1,
  "credits_charged": 2
}

The batch path always returns 201 — even when every row fails. Read created_count / rejected_count to drive your retry logic.

Request body

FieldTypeRequiredDescription
saved_account_idstring (UUID)yesA delivered account you own. Find IDs via GET /api/ext/accounts.
target_video_urlstringyesTikTok / Instagram video URL. The parser is lenient — protocol, vm.tiktok.com/..., tiktok.com/t/..., IG /share/..., instagram.com/reel/..., all work.
comment_textstringyesThe exact text the manager will post. Length capped per platform (TikTok 150, IG 2200).
brief_idstring (UUID)noOptional internal grouping ID — see your dashboard.

For the batch form, wrap in { "tasks": [ ... ] } (1–200 entries).

Validation rules

The same rules apply to single and batch:

  1. saved_account_id must belong to your user (SAVED_ACCOUNT_NOT_OWNED otherwise).
  2. The account must have an active manager (current_cm_id IS NOT NULL). If not, you'll see COMMENT_ACCOUNT_NOT_MANAGED (single) or account_not_managed (batch). Wait until an order is in progress on that account.
  3. target_video_url must parse to one of the supported platforms.
  4. The video's platform must match the account's platform — otherwise COMMENT_PLATFORM_MISMATCH (single) or platform_mismatch (batch).
  5. comment_text length must be 1–150 (TikTok), 1–2200 (IG).

Per-row rejection reasons (batch path)

rejected[*].reasonMeaning
account_not_foundsaved_account_id doesn't exist.
account_not_ownedAccount belongs to a different user.
account_not_managedAccount has no current_cm_id — no manager can take this task.
account_lookup_failedDB hiccup — retry the row.
invalid_video_urlURL didn't parse.
platform_mismatchURL platform ≠ account platform.
comment_text_too_longExceeded the platform's max chars.
comment_text_length_0_exceeds_XText is empty (legacy literal).
insufficient_creditsWallet ran out mid-batch. Top up and retry the rejected rows.

Tips

  • Idempotency: if you must retry a batch, dedupe locally on (saved_account_id, target_video_url, comment_text) first — TokPortal does not deduplicate identical calls. The same comment posted twice will create two pending tasks and bill two credits.
  • Don't post under-72h windows where you can't act. A pending task you don't follow up on auto-cancels at 72h and refunds, but you also burn a manager's mindshare.
  • Test on a single task first. The single-shot path returns precise typed errors, easier to debug than a partial batch.
  • CSV import? The dashboard's CSV importer hits the same engine — every rule above applies there too.