Skip to main content

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: tok_live_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: tok_live_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.