Public TikTok data over a small, predictable HTTP API.
**Every response uses the same envelope**, success or failure:
```json
{ "success": true, "data": {}, "cursor": null, "hasMore": false }
```
A failure keeps the shape and adds `error` with a stable `code` you can branch
on. Codes never change meaning; new ones may be added.
```json
{ "success": false, "data": null, "cursor": null, "hasMore": false,
"error": { "code": "not_found", "message": "…", "details": {} } }
```
**Error codes**
| HTTP | `error.code` | Meaning |
|---|---|---|
| 400 | `invalid_request` | A parameter failed validation; `details.fields` names each one |
| 400 | `unsupported_option` | The request asked for something this endpoint cannot serve |
| 403 | `private_account` | The account is private |
| 404 | `not_found`, `user_not_found`, `video_not_found` | TikTok has nothing by that id or handle |
| 405 | `method_not_allowed` | The path exists, the method does not |
| 429 | `quota_exceeded` | Your allowance is used up; retry later |
| 429 | `rate_limited` | TikTok is throttling; retry later |
| 502 | `upstream_unavailable`, `parse_error`, `listing_unavailable` | TikTok answered with something unusable, or refused |
| 503 | `upstream_recovering`, `challenge_detected` | TikTok is temporarily unavailable; retry later |
| 504 | `deadline_exceeded` | The request timed out |
| 500 | `internal_error` | Something on our side; the `message` says what |
**About the numbers.** TikTok rounds a video's play and like counts to three
significant digits on listings, at any size - 11,381 plays are published as
11,400 - so listings report `statsPrecision: approximate`. A profile's own
follower and like counts are exact, and so are the counts the music and effect
endpoints report. Comment, share and save counts arrive exact everywhere. Every
response says which it carries in `statsPrecision` rather than passing a
rounded number off as real.
**Pagination.** `cursor` and `hasMore` are present for shape consistency. Where
TikTok exposes no pagination, `hasMore` is always `false` and the response says
what the ceiling is - it will not invite you to page through nothing.