# GET /v1/post

One post from any of the four platforms, normalized to a single shape. Pass the post's URL; the platform is detected from it.

## Request

```bash
curl "https://tikdata.io/v1/post?url=https://www.tiktok.com/@tiktok/video/7680721699171601694" \
  -H "Authorization: Bearer thr_live_xxx"
```

| param | required | notes |
|---|---|---|
| `url` | yes | any TikTok, Instagram, YouTube or X post URL (URL-encode it) |
| `platform` | no | `tiktok` \| `instagram` \| `youtube` \| `x` — overrides detection |

Accepted URL forms:

| platform | forms |
|---|---|
| TikTok | `tiktok.com/@user/video/{id}`, `/photo/{id}`, `/v/{id}` |
| Instagram | `instagram.com/p/{code}/`, `/reel/{code}/`, `/tv/{code}/` |
| YouTube | `youtube.com/watch?v={id}`, `/shorts/{id}`, `/embed/{id}`, `youtu.be/{id}` |
| X | `x.com/{user}/status/{id}`, `twitter.com/{user}/status/{id}` |

TikTok short links (`tiktok.com/t/…`) are not resolved — follow the redirect first, or use `/v1/raw` with `fetch_one_video_by_share_url`.

## Response — 200

```json
{
  "platform": "tiktok",
  "id": "7680721699171601694",
  "url": "https://www.tiktok.com/@tiktok/video/7680721699171601694",
  "caption": "this summer was all about the classic hits…",
  "cover": "https://p16-common-sign.tiktokcdn-us.com/…",
  "createdAt": "2026-09-02T00:03:08.000Z",
  "author": { "handle": "tiktok", "name": "TikTok", "followers": 95663434 },
  "stats": {
    "views": 202473,
    "likes": 8267,
    "comments": 1261,
    "shares": 631,
    "saves": 922,
    "reposts": 0,
    "quotes": 0,
    "downloads": 40
  }
}
```

| field | type | notes |
|---|---|---|
| `platform` | string | detected or forced |
| `id` | string | the platform's own post id (Instagram: the shortcode) |
| `url` | string | the URL you passed |
| `caption` | string | `""` if none |
| `cover` | string | thumbnail URL; TikTok single-video covers are HEIC |
| `createdAt` | ISO 8601 \| null | |
| `author.handle` | string | bare handle, no `@` |
| `author.name` | string | display name |
| `author.followers` | integer | see per-platform notes |
| `stats.*` | integer | every engagement number the platform exposes; `0` where it does not |

## What each platform reports

| | views | likes | comments | shares | saves | reposts | quotes | downloads |
|---|---|---|---|---|---|---|---|---|
| **TikTok** | play_count | digg_count | comment_count | share_count | collect_count | repost_count | — | download_count |
| **Instagram** | play_count | like_count | comment_count | share_count | save_count | repost_count | — | — |
| **X** | views | likes | replies | retweets | bookmarks | retweets | quotes | — |
| **YouTube** | view_count | like_count | comment_count | — | — | — | — | — |

A dash means the platform does not expose that number publicly; the field is present and `0`.

On X a repost *is* the share mechanism, so `shares` and `reposts` carry the same number there by design — a caller summing shares across platforms gets a comparable figure instead of a `0`.

### Per-platform notes

- **TikTok** — `author.followers` is the author's live follower count. Cover images are HEIC; the feed endpoint's covers are webp if you need something vision models can decode.
- **Instagram** — uses the provider's `v2/fetch_post_info`, the only Instagram post endpoint that returns likes, shares, saves and reposts. It costs `$0.002` per call versus `$0.001` for the basic endpoint. `author.followers` is `0` — the post payload's user object does not carry it; use [`/v1/profile`](/docs/profile.md) for that.
- **X** — `author.followers` comes from the tweet's embedded author block. Numbers are read from the flattened top level, so a quoted or retweeted tweet's stats can never leak in.
- **YouTube** — `comments` is parsed from an abbreviated display string (`"2.4M"`) and is therefore rounded. `author.followers` is subscribers, also from a display string (`"4.54M subscribers"`).

## Missing vs gone

Missing *individual* stats are `0`, never an error — platforms omit different fields and a partial answer beats a failed refresh.

A **deleted or unknown post is a 404**, never a post with zeroed stats:

```json
{ "error": "post not found", "platform": "tiktok", "id": "9999999999999999999", "url": "…" }
```

The provider answers 200 with the post object omitted for dead content; the router checks for it explicitly. Private and deleted are not distinguishable — see [errors](/docs/errors.md).

## Errors

| status | body |
|---|---|
| 400 | `{"error":"url is required","hint":"GET /v1/post?url=https://www.tiktok.com/@x/video/123"}` |
| 400 | `{"error":"unrecognized url","hint":"supported: tiktok, instagram, youtube, x"}` |
| 400 | `{"error":"could not extract a tiktok post id from that url"}` |
| 404 | `{"error":"post not found", …}` |
| 502 | `{"error":"upstream error","status":500,"platform":"…","upstream":{…}}` or `{"error":"upstream unreachable"}` |

## Fallbacks

TikTok tries three independent backends, Instagram two, YouTube two; X has one (no working alternate exists on this node, so it retries instead). A backend that answers 200 with nothing is treated as blind and the next is tried; a 5xx or dropped connection is retried on the same one. `x-router-source` and `x-router-attempts` on the response say which answered and what it cost. Full detail in [errors](/docs/errors.md).

## Billing

One upstream call per request when the first backend answers — the common case. A post that no backend can serve costs the whole chain (3 calls on TikTok) before returning 404, which is the price of never reporting a live post as deleted. Billed by the provider whether or not the post still exists. Instagram costs $0.002 rather than $0.001 (see above).
