Errors

Every error is JSON with an error string and, where it helps, a hint:

{ "error": "too many urls", "hint": "max 500 per request, got 501" }

HTTP statuses

status when
400 bad input — missing url, unparseable body, too many items, unrecognized platform, days out of range
401 missing, unknown or disabled key; admin endpoint without the admin token
403 scoped key reaching outside its scopes (/v1/raw only)
404 the post or profile does not exist, is deleted, or is private — never a zeroed result
405 wrong method (e.g. GET on /v1/posts)
502 upstream unreachable, or upstream returned an error we could not turn into a 404

404 is a feature

The upstream provider answers HTTP 200 for a deleted post and simply omits the post object. Read naively, a removed video looks like a genuine crash to 0 views — which, if the numbers feed view tracking, is a payout dispute waiting to happen.

TikData checks for the object explicitly and answers 404 instead. The same applies to profiles: X returns 200 with status: "notfound" and Instagram returns 200 with status: false for accounts that do not exist, and both become 404 here.

Missing individual stats still come back as 0 inside an otherwise-successful response — platforms omit different fields, and a partial answer beats a failed refresh.

Private vs deleted

Not distinguishable through the upstream provider. Verified 2026-09-08 against a known-private TikTok video across seven endpoints: every one returns a byte-identical body for private and for a bad id. The signal exists at TikTok itself (statusMsg: "status_self_see" vs "item doesn't exist") but is stripped before it reaches us. See videos/bulk_stats for the per-item error codes this leaves us with.

Per-item errors in batch endpoints

POST /v1/posts and POST /v1/videos/bulk_stats never fail the whole request for one bad item. Each result carries ok: true|false, and failed items carry an error code:

code meaning
not_found well-formed reference, but the platform returned no post (deleted, private, or never existed)
invalid_id the id is malformed — TikTok ids encode a creation timestamp, so this is decided offline with no upstream call
unsupported_platform platform was given but is not tiktok, instagram, youtube or x
bad_input neither a usable id nor url, or no platform could be determined
upstream_error the provider returned a non-2xx we could not classify
upstream_unreachable timeout or network failure reaching the provider

Upstream flakiness, and how TikData handles it

The provider fails in two different ways, and they need opposite responses.

The node wobbles. 5xx, dropped connections and the provider's own 400 "Request failed. Please retry." arrive in bursts that hit every endpoint and every platform at once — a run of ~100 calls pushed the whole account into 502s for about 40 seconds on 2026-09-12, TikTok, X, Instagram and YouTube alike. Switching endpoints cannot help with that; only waiting can. So these retry the same endpoint with backoff (2 retries, 400 ms growing).

One backend goes blind. A 200 arrives with the post object simply missing, while a sibling endpoint on a different backend still has it. Retrying the same path is pointless. So these move to the next endpoint in the chain.

The chains

Every entry was verified live against a known-good subject on 2026-09-12.

operation TikTok Instagram YouTube X
post 3 backends 2 (v3 at $0.008 last) 2 1 — retry only
profile 1 — retry only 3 1 1 — retry only
feed 3 3 1 2

Where the table says "1", no working alternate exists on this node: the siblings are 404, or answer 200 without the field that matters (X's fetch_user_about returns no follower count, so it cannot stand in for fetch_user_profile).

What a failed lookup costs

A blind backend is indistinguishable from a genuinely deleted post — both answer 200-with-nothing. Walking the whole chain is what separates them: if every backend says nothing, it really is gone. So a deleted TikTok post costs 3 calls rather than 1. That is the price of never reporting a live post as deleted, and malformed ids are still rejected offline for free as invalid_id.

Feed walks pick a backend on page one and stay on it, because paging cursors are endpoint-specific.

Seeing which backend answered

/v1/post and /v1/profile return two headers:

header meaning
x-router-source the upstream path that produced the data
x-router-attempts total upstream requests made, including retries and fallbacks

/v1/creator/stats reports the same thing in its source field.