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
| Field | Type | Required | Description |
|---|---|---|---|
saved_account_id | string (UUID) | yes | A delivered account you own. Find IDs via GET /api/ext/accounts. |
target_video_url | string | yes | TikTok / Instagram video URL. The parser is lenient — protocol, vm.tiktok.com/..., tiktok.com/t/..., IG /share/..., instagram.com/reel/..., all work. |
comment_text | string | yes | The exact text the manager will post. Length capped per platform (TikTok 150, IG 2200). |
brief_id | string (UUID) | no | Optional 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:
saved_account_idmust belong to your user (SAVED_ACCOUNT_NOT_OWNEDotherwise).- The account must have an active manager (
current_cm_id IS NOT NULL). If not, you'll seeCOMMENT_ACCOUNT_NOT_MANAGED(single) oraccount_not_managed(batch). Wait until an order is in progress on that account. target_video_urlmust parse to one of the supported platforms.- The video's platform must match the account's platform — otherwise
COMMENT_PLATFORM_MISMATCH(single) orplatform_mismatch(batch). comment_textlength must be 1–150 (TikTok), 1–2200 (IG).
Per-row rejection reasons (batch path)
rejected[*].reason | Meaning |
|---|---|
account_not_found | saved_account_id doesn't exist. |
account_not_owned | Account belongs to a different user. |
account_not_managed | Account has no current_cm_id — no manager can take this task. |
account_lookup_failed | DB hiccup — retry the row. |
invalid_video_url | URL didn't parse. |
platform_mismatch | URL platform ≠ account platform. |
comment_text_too_long | Exceeded the platform's max chars. |
comment_text_length_0_exceeds_X | Text is empty (legacy literal). |
insufficient_credits | Wallet 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
pendingtask 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.