# GET /v1/creator/stats

Followers now, plus everything a creator published in the last N days and how it performed. One request whatever the paging underneath — the walk follows each platform's cursor until a page reaches past the window, the feed runs out, or a six-page cap stops it.

## Read this first

**These are views on posts *published* in the window, not views *accrued* during it.** Platforms only ever report a post's lifetime view count, so an accrued figure would need a daily snapshot of every post to difference against. That is stored history, which TikData deliberately does not keep. The response says so in `window.basis`.

## Request

```bash
curl "https://tikdata.io/v1/creator/stats?url=https://www.tiktok.com/@tiktok&days=30" \
  -H "Authorization: Bearer thr_live_xxx"
```

| param | default | notes |
|---|---|---|
| `url` | required (or `handle` + `platform`) | a profile URL, as for [`/v1/profile`](/docs/profile.md) |
| `days` | `30` | 1–365 |
| `platform` | detected | override, or required with `handle` |

## Response — 200

```json
{
  "platform": "tiktok",
  "handle": "tiktok",
  "url": "https://www.tiktok.com/@tiktok",
  "name": "TikTok",
  "followers": 95640964,
  "window": {
    "days": 30,
    "since": "2026-08-09T16:01:22.000Z",
    "until": "2026-09-08T16:01:22.000Z",
    "basis": "posts published in the window (not views accrued during it)",
    "dates": "exact"
  },
  "totals": {
    "posts": 14,
    "views": 3719724,
    "likes": 176298,
    "comments": 19685,
    "shares": 11436,
    "saves": 8190,
    "reposts": 0,
    "quotes": 0
  },
  "averages": { "viewsPerPost": 265695, "postsPerWeek": 3.27 },
  "topPosts": [
    { "id": "7676134473112489247",
      "url": "https://www.tiktok.com/@tiktok/video/7676134473112489247",
      "created_at": "2026-08-20T15:22:21Z",
      "views": 573105, "likes": 30110, "comments": 2791, "shares": 1340,
      "saves": 1102, "reposts": 0, "quotes": 0 }
  ],
  "coverage": { "pagesFetched": 1, "postsScanned": 20, "truncated": false },
  "billedCalls": 2
}
```

| field | notes |
|---|---|
| `followers` | from the profile call made first |
| `window.dates` | `"exact"` or `"approximate"` (YouTube — see below) |
| `totals.*` | summed over in-window posts; per-platform availability follows the [`/v1/post`](/docs/post.md) table (no `downloads` in feeds) |
| `averages.postsPerWeek` | `posts / days × 7` |
| `topPosts` | up to 10, by views |
| `coverage.truncated` | **true** means the six-page cap was hit before reaching the window edge — the totals are a **floor**, not a total; a `note` explains |
| `billedCalls` | profile + feed pages (+1 to resolve a YouTube `@handle`) |

## Verified live

| | window | followers | posts | views | calls |
|---|---|---|---|---|---|
| TikTok @tiktok | 30d | 95.6M | 14 | 3,719,724 | 2 |
| Instagram @instagram | 30d | 686.5M | 9 | 898,664,129 | 3 |
| X @nasa | 7d | 92.4M | 8 | 4,628,396 | 2 |
| YouTube @MrBeast | 90d | 516M | 6 | 504,000,000 | 3 |

## Per-platform notes

- **TikTok** pages on `max_cursor`. `has_more` lies, so the dates decide when to stop.
- **Instagram** uses `fetch_user_posts`, not `fetch_user_reels` — only the former returns `next_max_id`/`more_available`, and it covers reels as well as grid posts. Items come back **out of order with pinned posts floated to the top**, so a single old item does not end the walk; only a page with nothing in the window does. The list payload carries no share/save/repost counts, so those totals are `0` for Instagram here even though [`/v1/post`](/docs/post.md) has them.
- **YouTube** — the channel listing gives **relative** dates (`"2 months ago"`), not timestamps, so `window.dates` is `"approximate"`: days and weeks are precise enough for a 30-day question, months are not. The listing carries no per-post likes or comments. Without `language_code=en` the payload is Chinese-localised (`7.3万次观看`) — handled.
- **X** uses `fetch_user_media`, which is the right list anyway since clips are media posts.

## Cost bound

At most **6 feed pages** (+1 profile, +1 YouTube resolve) per request. Page sizes observed live: TikTok 20, Instagram 12, YouTube 30, X ~19. For a creator posting more than ~120 times in the window on TikTok, expect `truncated: true`.

## Errors

| status | body |
|---|---|
| 400 | `{"error":"url is required","hint":"GET /v1/creator/stats?url=https://www.tiktok.com/@tiktok&days=30"}` |
| 400 | `{"error":"days must be between 1 and 365"}` |
| 400 | `{"error":"unknown platform", …}` / `{"error":"unrecognized url", …}` / `{"error":"could not extract a … handle from that url"}` |
| 404 | `{"error":"profile not found","platform":"…","handle":"…"}` |
| 502 | `{"error":"feed unavailable","platform":"…","handle":"…","followers":N,"upstreamStatus":400,"hint":"…"}` — the profile resolved but the post list could not be read after one retry. This is a 502 on purpose: returning 200 with zero posts for a creator who posts daily would be the false-zero this whole API exists to prevent. |
| 502 | `{"error":"upstream unreachable"}` / `{"error":"could not resolve a … feed id for @…"}` |

## Retries

Two upstream flakes get exactly one retry each, on any feed page: Instagram's 200-with-`"This account does not exist."` for accounts that exist, and the provider's 400 whose body literally says `"Please retry"`. A retried 200 is still billed upstream and shows in `billedCalls`. Nothing else retries.
