# Track creators and videos

> Watch a creator or a single video over time. Virlo re-checks it on your schedule, saves the numbers, and writes an AI report each time.

Source: https://dev.virlo.ai/docs/tracking
Markdown: https://dev.virlo.ai/docs/tracking.md
Section: Tracking

## About the Virlo API (applies to every page)

- Base URL: `https://api.virlo.ai/v1`. Every request needs the header `Authorization: Bearer YOUR_API_KEY` (keys start with `virlo_tkn_`).
- Responses are JSON inside a `data` field, except the `/v1/webhooks` endpoints, which return the object or array directly. Field names are snake_case.
- Prices are in US dollars from a prepaid balance. 1 credit = $0.01. The `X-Cost` response header on each successful response is the exact charge. Errors are free.
- Slow jobs return an ID. Check its status every 15 seconds (or whatever `retry_after_seconds` says) until `finalized` is `true`.
- All docs pages: https://dev.virlo.ai/llms.txt. Every page in one file: https://dev.virlo.ai/llms-full.txt. MCP server for AI assistants: https://dev.virlo.ai/api/mcp/mcp.

---

Watch competitors, vet a creator before a paid deal, or see how a sponsored post performs. Virlo re-checks a TikTok, YouTube, or Instagram creator or video on your schedule.

**At a glance**

- **What it does:** Watches a creator or one video, and writes a fresh AI report on each check.
- **You send:** A creator's handle or profile link, or a video link, plus how often to check.
- **You get back:** An ID right away. Then numbers, history, AI reports, and alerts such as breakout videos.
- **Cost:** $0.25 to start, then $0.25 per check until you pause or stop. Reading is free.
- **How long:** The first report takes 1 to 2 minutes for a creator, and under a minute for a video.

---

## How tracking works

1. **Start** with [Track a creator](https://dev.virlo.ai/docs/tracking#track-a-creator) or [Track a video](https://dev.virlo.ai/docs/tracking#track-a-video). You get an `id` and pay $0.25, which covers the first check.
2. **Wait for the first check.** Ask for the item again every 15 seconds, or whatever `retry_after_seconds` says (free; this is called polling), until `enrichment_status` is `ready` or `failed`. Or use the `tracking.cycle.completed` [webhook](https://dev.virlo.ai/docs/tracking#webhook-notifications).
3. **Read the results, free:** [snapshots](https://dev.virlo.ai/docs/tracking#get-creator-snapshots) (history), the [AI report](https://dev.virlo.ai/docs/tracking#get-creator-report), [signals](https://dev.virlo.ai/docs/tracking#get-creator-signals) (alerts), and [posts](https://dev.virlo.ai/docs/tracking#list-creator-posts).
4. **It repeats** at $0.25 per check until you [pause](https://dev.virlo.ai/docs/tracking#update-tracked-creator) or [stop](https://dev.virlo.ai/docs/tracking#stop-tracking-creator).

| Field | Values |
| - | - |
| `enrichment_status` | Report progress: `pending`, `processing`, `ready`, or `failed`. **`failed`:** 3 attempts in a row failed (for example, a wrong handle). A new item shows this within about a minute. Tracking pauses, and a failed first check is refunded. |
| `status` | `active`, `paused` (no checks or charges: you paused it, or Virlo did after 3 failures or a low balance), or `deleted` (stopped, but still readable by `id`). |

A **check** (the API also says _cycle_ or _scrape_) fetches the latest numbers, saves them as a **snapshot**, and writes an AI report. An _audience snapshot_ is different: a $0.50 profile of a creator's commenters.

**Details for developers**

Base URL: `https://api.virlo.ai/v1/tracking`. Results come in `data`, and lists add `pagination`. Times are ISO 8601 in UTC. Unknown parameters return `400`. Rates are fractions (`0.05` = 5%) unless noted. Tracked creators also have `pending_jobs` and `finalized`, as in [How results load](https://dev.virlo.ai/docs/async-data). Tracked videos don't.

**Images.** For tracked creators, `avatar_url`, a post's `thumbnail_url`, and a sound's `cover_url` are file names. Put `https://auth.virlo.ai/storage/v1/object/public/` plus `avatars/`, `thumbnails/`, or `sound-covers/` in front. A value that starts with `https://` is already a link. Tracked videos return platform links, and TikTok's expire.

---

## What it costs

- **Start:** $0.25, covering the first check. Refunded if Virlo can't find the creator or video.
- **Each later check:** $0.25, charged only when it succeeds.
- **Extras:** [older posts](https://dev.virlo.ai/docs/tracking#collect-creator-posts) $0.50 to $2.00, and an [audience snapshot](https://dev.virlo.ai/docs/tracking#audience-basics) $0.50.
- **Everything else** is free.

Later checks are billed silently (only the start charge appears in the `X-Cost` header), so watch your [balance](https://dev.virlo.ai/docs/credits#check-your-balance) and [Usage](https://dev.virlo.ai/dashboard/usage) page. If your balance can't cover a check, tracking pauses and won't restart when you add funds: [resume it](https://dev.virlo.ai/docs/tracking#update-tracked-creator) yourself.

---

## Track a creator

**Endpoint:** `POST https://api.virlo.ai/v1/tracking/creators`

Starts tracking a creator and runs the first check right away.

**Cost:** $0.25. Refunded within about a minute if Virlo can't find the creator.

**How long:** 1 to 2 minutes for the first report. Poll [Get tracked creator](https://dev.virlo.ai/docs/tracking#get-tracked-creator).

Each creator can be tracked once per account, across the web app and the API. If it's already tracked, you get a `409` and no charge. The `message` says where. If it's in this key's workspace, you also get its `creator_id`. A creator you stopped through the API is restored instead.

### Request body

- `platform` (string, required): `tiktok`, `youtube`, or `instagram`, in lowercase.
- `handle` (string, optional): Such as `khaby.lame`. Send `handle` or `url`. If both, `handle` wins.
- `url` (string, optional): The profile link, with `https://`: `https://www.tiktok.com/@username`, `https://www.instagram.com/username`, or `https://www.youtube.com/@handle` (`/channel/`, `/c/`, and `/user/` links work too).
- `scrape_cadence` (string, optional): `six_hours`, `twelve_hours`, `daily` (default), `every_other_day`, `weekly`, `bi_weekly`, or `monthly`.
- `collection_depth` (string, optional): Also collect older posts once: `standard` (50 videos, +$0.50), `deep` (200, +$1.00) or `full` (500, +$2.00). Refunded if it finds no posts or fails.

**cURL request:**

```bash
curl -X POST https://api.virlo.ai/v1/tracking/creators \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "tiktok",
    "handle": "khaby.lame",
    "scrape_cadence": "daily"
  }'
```

**JavaScript request:**

```js
const response = await fetch(
  'https://api.virlo.ai/v1/tracking/creators',
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      platform: 'tiktok',
      handle: 'khaby.lame',
      scrape_cadence: 'daily'
    })
  }
);
const data = await response.json();
```

**Python request:**

```python
import requests

response = requests.post(
    'https://api.virlo.ai/v1/tracking/creators',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    json={
        'platform': 'tiktok',
        'handle': 'khaby.lame',
        'scrape_cadence': 'daily'
    }
)
data = response.json()
```

**Response 202:**

```json
{
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "active",
    "message": "Creator tracking started. Initial metrics and AI report are being generated.",
    "pending_jobs": [
      {
        "type": "tracking_report",
        "status": "pending",
        "poll_url": "/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "result_path": "data",
        "webhook_event": "tracking.cycle.completed",
        "retry_after_seconds": 15
      }
    ],
    "finalized": false
  }
}
```

**Response 409 Already tracked:**

```json
{
  "statusCode": 409,
  "error": "Conflict",
  "message": "You're already tracking @khaby.lame.",
  "creator_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "code": "conflict"
}
```

**Response 402:**

```json
{
  "statusCode": 402,
  "message": "Insufficient balance. $0.25 required, $0.12 remaining. Add funds at https://dev.virlo.ai/dashboard/billing",
  "error": "Payment Required",
  "required_credits": 25,
  "remaining_credits": 12,
  "required_amount": "$0.25",
  "remaining_balance": "$0.12",
  "code": "insufficient_credits"
}
```

**Response 402 With depth:**

```json
{
  "statusCode": 402,
  "message": "Insufficient credits",
  "error": "Payment Required",
  "required_credits": 125,
  "remaining_credits": 40,
  "code": "insufficient_credits"
}
```

A `402` has two shapes. Both have `code` and `required_credits`.

---

## List tracked creators

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators`

Creators your team tracks through the API, newest first, including paused ones. Stopped creators and creators tracked in the web app aren't listed. Each item is a full creator.

**Cost:** Free.

### Query parameters

- `search` (string, optional): Part of a handle or display name.
- `platform` (string, optional): `tiktok`, `youtube`, or `instagram`.
- `page` (integer, optional): Default `1`.
- `limit` (integer, optional): Default `20`. Over 100 counts as 100.

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/tracking/creators \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d search=khaby
```

**Response 200:**

```json
{
  "data": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "tiktok",
      "platform_handle": "khaby.lame",
      "status": "active",
      "scrape_cadence": "daily",
      "enrichment_status": "ready",
      "latest_followers": 162973075,
      "growth_rate": 0.00031009,
      "next_scrape_at": "2026-09-25T14:00:05.120+00:00"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "total_pages": 1,
    "has_next_page": false,
    "has_prev_page": false
  }
}
```

---

## Get tracked creator

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id`

One tracked creator: latest numbers, settings, and check progress.

**Cost:** Free.

**Full field reference**

Every field is in the example. Notes:

- `growth_rate`: change since the last check, in total views on YouTube and followers elsewhere. It reads `1` after the first check.
- `followers_gained`: equals all followers after the first check.
- `latest_total_likes`: `0` on YouTube and Instagram.
- `latest_total_views`: the channel total on YouTube. Elsewhere, the sum over stored posts.
- `profile_metadata`: differs by platform. `external_links` is an object.
- `next_scrape_at`: the check starts within about 5 minutes of this time.

**cURL request:**

```bash
curl https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "platform": "tiktok",
    "platform_handle": "khaby.lame",
    "profile_url": "https://www.tiktok.com/@khaby.lame",
    "display_name": "Khabane lame",
    "avatar_url": "7c2e9f4b1a8d3c6e5f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e.jpg",
    "bio": "If u wanna laugh u r in the right place",
    "is_verified": true,
    "status": "active",
    "scrape_cadence": "daily",
    "enrichment_status": "ready",
    "latest_followers": 162973075,
    "latest_following": 81,
    "latest_total_likes": 2681204805,
    "latest_total_views": 463241900,
    "latest_total_videos": 1355,
    "last_scraped_at": "2026-09-24T14:00:05.120+00:00",
    "growth_rate": 0.00031009,
    "followers_gained": 50521,
    "next_scrape_at": "2026-09-25T14:00:05.120+00:00",
    "category": "comedy",
    "content_tags": ["silent-comedy", "life-hacks", "reactions"],
    "profile_metadata": {
      "country": null,
      "website": null,
      "language": "en",
      "external_links": {}
    },
    "created_at": "2026-09-20T10:30:00.000+00:00",
    "updated_at": "2026-09-24T14:00:31.402+00:00",
    "pending_jobs": [],
    "finalized": true
  }
}
```

**Response 404:**

```json
{
  "message": "Tracked creator not found",
  "error": "Not Found",
  "statusCode": 404,
  "code": "not_found"
}
```

---

## Get creator report

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id/report`

The latest AI report on the creator, rewritten each check: what works, breakout videos (`viral_content`), and how commenters react. `report` is `null` until the first check is done. **Cost:** Free. `collection_data` lists the videos the report used. `popular_videos` is TikTok only, and YouTube uses `shorts`.

**cURL request:**

```bash
curl https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/report \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "data": {
    "account": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "tiktok",
      "platform_handle": "khaby.lame",
      "latest_followers": 162973075
    },
    "report": {
      "id": "c7d8e9f0-a1b2-4c3d-9e4f-5a6b7c8d9e0f",
      "tracking_account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "analysis": {
        "overview": {
          "headline": "163M-follower comedy creator known for silent reactions...",
          "recent_focus": "Everyday mishaps and travel gags...",
          "content_focus": "Language-free comedy that works in any country...",
          "popular_focus": "Silent reactions to overcomplicated life hacks...",
          "growth_insight": "Follower growth is steady but slowing..."
        },
        "key_insight": "Wordless storytelling travels across languages...",
        "what_works": [
          {
            "insight": "Silent, non-verbal storytelling",
            "evidence": "His top videos contain no spoken words...",
            "evidence_video_ids": ["7678009073421405471"]
          }
        ],
        "discoveries": [
          { "text": "Posts at almost the same time of day...", "metric": "3 posts/week", "evidence_video_ids": [] }
        ],
        "viral_content": [
          {
            "title": "They were there yesterday, I promise #learnfromkhaby #comedy",
            "views": 101400000,
            "outlier_ratio": 8.52,
            "why_it_worked": "A relatable mix-up with a silent payoff...",
            "platform_video_id": "7678009073421405471"
          }
        ],
        "content_themes": [
          {
            "name": "Life hack reactions",
            "avg_views": 89400000,
            "description": "Watches an overcomplicated solution, then shows the simple one...",
            "video_count": 8,
            "evidence_video_ids": ["7678009073421405471"]
          }
        ],
        "posting_patterns": {
          "frequency": "2 to 3 videos per week...",
          "best_formats": ["Silent reaction to a life hack"],
          "optimal_duration": "13 to 18 seconds"
        },
        "subjects_covered": ["Life hack reactions", "Travel mishaps"],
        "audience_sentiment": {
          "overall_summary": "Overwhelmingly positive and global...",
          "sentiment_labels": ["highly_positive", "appreciative"],
          "notable_comments": [
            { "likes": 190974, "content": "Learn from Khaby", "insight": "Fans treat him as a teacher of common sense", "video_id": "7678009073421405471" }
          ],
          "key_findings": [
            { "finding": "Comments come in many languages...", "takeaway": "Keep videos wordless to reach every market..." }
          ]
        }
      },
      "collection_data": {
        "latest_videos": [
          {
            "url": "https://www.tiktok.com/@khaby.lame/video/7688400123809893662",
            "title": "Yeah, I'm never taking these glasses off again. #learnfromkhaby",
            "views": 1100000,
            "likes": 64000,
            "comments": 820,
            "shares": 1900,
            "bookmarks": 3100,
            "duration": null,
            "published_at": "2026-09-22T16:39:49.000Z",
            "thumbnail_url": "0174d2837f7635b2bed46a3bff5e03ec16fe26465600f3839f56b68e69911c2a.webp",
            "platform_video_id": "7688400123809893662"
          }
        ],
        "popular_videos": [
          {
            "url": "https://www.tiktok.com/@khaby.lame/video/7678009073421405471",
            "title": "They were there yesterday, I promise #learnfromkhaby #comedy",
            "views": 101400000,
            "likes": 6200000,
            "comments": 31000,
            "shares": 450000,
            "bookmarks": 120000,
            "duration": null,
            "published_at": "2026-08-25T16:36:58.000Z",
            "thumbnail_url": "6893372b005aa6d33df99bfd7c8093090c52ead2dc2a83c346972e8a749f47c1.webp",
            "platform_video_id": "7678009073421405471"
          }
        ],
        "shorts": [],
        "shorts_count": 0,
        "comments_collected": 40,
        "transcripts_collected": 0,
        "top_video_transcripts": {}
      },
      "created_at": "2026-09-24T14:00:31.402+00:00"
    }
  }
}
```

---

## Get creator signals

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id/signals`

Alerts from the creator's checks, newest first.

**Cost:** Free.

| `type` | Raised when |
| - | - |
| `outlier_video` | A video got over 3 times the median views. Once per video. |
| `follower_spike`, `follower_decline` | Followers moved at least 3% and 250 followers since the last check (`critical` at 10% or more). `payload.pct` is a percent. |
| `new_subject` | The new report covers new topics. |
| `creator_unreachable` | 3 failed attempts paused tracking. |

`weighted_score` ranks breakouts: it rises with both `outlier_ratio` and median views. It's not the agent pages' [Virality Score](https://dev.virlo.ai/docs/glossary#words-that-change-meaning).

`read` is always `false`. To spot new alerts, keep the newest `created_at` you've seen.

### Query parameters

- `limit` (integer, optional): Default `50`, at most 100. There's no paging or date filter.

**cURL request:**

```bash
curl "https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/signals?limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "data": [
    {
      "id": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
      "type": "outlier_video",
      "severity": "notable",
      "title": "Outlier video: They were there yesterday, I promise #learnfromkhaby #comedy",
      "payload": {
        "url": "https://www.tiktok.com/@khaby.lame/video/7678009073421405471",
        "title": "They were there yesterday, I promise #learnfromkhaby #comedy",
        "views": 101400000,
        "platform": "tiktok",
        "median_views": 11900000,
        "outlier_ratio": 8.52,
        "weighted_score": 34.91,
        "platform_handle": "khaby.lame",
        "platform_video_id": "7678009073421405471"
      },
      "created_at": "2026-08-26T14:01:12.431+00:00",
      "read": false
    }
  ]
}
```

---

## Get creator snapshots

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id/snapshots`

The creator's numbers at each check, one row per check.

**Cost:** Free.

You get the most recent `limit` checks in your date range, listed oldest first. There's no paging.

- `delta_*` compares with the check before it. The first row's are `null` only when there's no earlier check.
- `engagement_rate` isn't the usual metric: it's `total_likes` ÷ `total_views` (`null` if views are 0). On TikTok that's profile likes over about 20 stored posts' views, so values like `5.79` are common. Use it only as one creator's trend. It's `0` on YouTube and Instagram.
- `subscribers` repeats `followers` on YouTube and is `null` elsewhere.

### Query parameters

- `start_date` (string, optional): ISO 8601, such as `2026-09-01`.
- `end_date` (string, optional): A date alone means the start of that day, so that day is left out. Add `T23:59:59Z` to include it.
- `limit` (integer, optional): How many of the most recent checks to return, 1 to 365. Default `30`.

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/snapshots \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d start_date=2026-09-17T00:00:00Z
```

**Response 200:**

```json
{
  "data": [
    {
      "id": "1b2c3d4e-5f6a-4b7c-9d8e-0f1a2b3c4d5e",
      "tracking_account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "followers": 162973075,
      "following": 81,
      "subscribers": null,
      "total_videos": 1355,
      "total_views": 463241900,
      "total_likes": 2681204805,
      "collected_post_count": 20,
      "snapshot_at": "2026-09-24T14:00:05.120+00:00",
      "engagement_rate": 5.787915,
      "delta_followers": 50521,
      "delta_following": 0,
      "delta_total_videos": 1,
      "delta_total_views": 1253800,
      "delta_total_likes": 1254805,
      "delta_engagement_rate": -0.012992
    }
  ]
}
```

**Response 400 Bad date:**

```json
{
  "message": ["start_date must be a valid ISO 8601 date string"],
  "error": "Bad Request",
  "statusCode": 400,
  "code": "invalid_date_range"
}
```

---

## Update tracked creator

**Endpoint:** `PATCH https://api.virlo.ai/v1/tracking/creators/:id`

Pause, resume, or change the cadence. Send only what changes. The response is the full creator.

**Cost:** Free, but resuming triggers a $0.25 check.

- Resuming (`status: "active"`) runs that check now, or within an hour if the last one was under an hour ago.
- A new `scrape_cadence` sets the next check one full interval from now. Send it with `status` to resume without an immediate check.

### Request body

The handle can't change: stop tracking and track the new one.

- `status` (string, optional): `active` or `paused`.
- `scrape_cadence` (string, optional): As in [Track a creator](https://dev.virlo.ai/docs/tracking#track-a-creator).

**cURL request:**

```bash
curl -X PATCH https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "scrape_cadence": "weekly" }'
```

**Response 200:**

```json
{
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "platform": "tiktok",
    "platform_handle": "khaby.lame",
    "status": "active",
    "scrape_cadence": "weekly",
    "next_scrape_at": "2026-10-01T16:20:41.918+00:00"
  }
}
```

**Response 400:**

```json
{
  "message": ["status must be one of the following values: active, paused"],
  "error": "Bad Request",
  "statusCode": 400,
  "code": "validation_error"
}
```

---

## Stop tracking creator

**Endpoint:** `DELETE https://api.virlo.ai/v1/tracking/creators/:id`

Stops checks and charges, and hides the creator from your list. To take a break, [pause](https://dev.virlo.ai/docs/tracking#update-tracked-creator) instead.

**Cost:** Free.

- Everything stays readable by `id`, with `status: "deleted"`.
- To come back, track the handle again (same `id` and history, $0.25), or update the `id` to `paused` or `active`. If the last check was under an hour ago, the new one can take up to an hour to start.
- A second DELETE also returns `204`.

**cURL request:**

```bash
curl -X DELETE https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 204 No Content:**

```text
(empty response body)
```

---

## List creator posts

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id/posts`

The creator's stored videos, with their stats.

**Cost:** Free.

**Not the full history.** Each check stores TikTok's 10 or so newest and 10 most popular videos, up to Instagram's 90 newest, or YouTube's Shorts only. For older videos, [collect posts](https://dev.virlo.ai/docs/tracking#collect-creator-posts) with `deep` or `full`.

### Query parameters

- `sort` (string, optional): `publish_date_desc` (default), `publish_date_asc`, or `views_desc`.
- `start_date` (string, optional): Published on or after. ISO 8601.
- `end_date` (string, optional): Published on or before.
- `page` (integer, optional): Default `1`.
- `limit` (integer, optional): 1 to 200. Default `50`.

**Full field reference**

Every field is in the example. Notes:

- `is_outlier`: over 3 times the median views on the check that stored it. Otherwise `outlier_ratio` and `outlier_weighted_score` are `null`.
- `shares` and `bookmarks`: TikTok only, `0` elsewhere.
- `duration_seconds`: `null` on TikTok.
- `sound`: `external_id` is the platform's sound ID. YouTube posts get a sound only from `deep` or `full` collections. `usage_count` and `owner_handle` are often `0` or `null`.

**cURL request:**

```bash
curl -G "https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/posts" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d limit=20 \
  -d sort=views_desc
```

**Response 200:**

```json
{
  "data": [
    {
      "id": "c3d4e5f6-a7b8-4901-8def-123456789012",
      "tracking_account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "tiktok",
      "platform_video_id": "7678009073421405471",
      "url": "https://www.tiktok.com/@khaby.lame/video/7678009073421405471",
      "title": "They were there yesterday, I promise #learnfromkhaby #comedy",
      "description": "They were there yesterday, I promise #learnfromkhaby #comedy",
      "thumbnail_url": "6893372b005aa6d33df99bfd7c8093090c52ead2dc2a83c346972e8a749f47c1.webp",
      "publish_date": "2026-08-25T16:36:58+00:00",
      "views": 101400000,
      "likes": 6200000,
      "comments": 31000,
      "shares": 450000,
      "bookmarks": 120000,
      "is_duet": false,
      "is_stitch": false,
      "duration_seconds": null,
      "hashtags": ["learnfromkhaby", "comedy"],
      "collected_at": "2026-09-24T14:00:21.469+00:00",
      "is_outlier": true,
      "outlier_ratio": 8.52,
      "outlier_weighted_score": 34.91,
      "sound": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "title": "original sound - khaby.lame",
        "duration": 15,
        "platform": "tiktok",
        "cover_url": "abb4e3bcf8d355936aa54a444b83930591647f6c412843f9fa40dbb71478ce9a.jpg",
        "external_id": "7559312683885201425",
        "is_original": true,
        "usage_count": 0,
        "owner_handle": null,
        "owner_nickname": "Khabane lame",
        "is_commerce_music": false
      }
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 20,
    "total_pages": 1,
    "has_next_page": false,
    "has_prev_page": false
  }
}
```

---

## Get creator post

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id/posts/:post_id`

One stored post, with the same fields as the list.

**Cost:** Free.

### Path parameters

- `post_id` (string, required): The post's `id` from [List creator posts](https://dev.virlo.ai/docs/tracking#list-creator-posts). A platform video ID returns `404`.

**cURL request:**

```bash
curl "https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/posts/c3d4e5f6-a7b8-4901-8def-123456789012" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "data": {
    "id": "c3d4e5f6-a7b8-4901-8def-123456789012",
    "platform_video_id": "7678009073421405471",
    "url": "https://www.tiktok.com/@khaby.lame/video/7678009073421405471",
    "views": 101400000,
    "is_outlier": true,
    "outlier_ratio": 8.52,
    "outlier_weighted_score": 34.91
  }
}
```

**Response 404:**

```json
{
  "message": "Creator post not found",
  "error": "Not Found",
  "statusCode": 404,
  "code": "not_found"
}
```

---

## Collect creator posts

**Endpoint:** `POST https://api.virlo.ai/v1/tracking/creators/:id/posts/collect`

Stores a creator's older videos once, for more history than checks keep.

**Cost:** set by `depth`, charged when it starts. Refunded in full if it finds no posts or its last attempt fails.

| `depth` | Gets | Cost |
| - | - | - |
| `standard` (default) | Up to 50 videos | $0.50 |
| `deep` | Up to 200 videos | $1.00 |
| `full` | Up to 500 videos | $2.00 |

**How long:** seconds for `standard`, longer for `deep` and `full`. Poll [Get collection status](https://dev.virlo.ai/docs/tracking#get-collection-status).

- **Every depth pages back to its target.** On TikTok it also adds the most popular videos: one page at `standard`, up to 100 at `deep` and `full`.
- **Wait for a successful check first** (`enrichment_status: "ready"`). If the last check failed, you get a `409` and no charge.
- One collection at a time. Another returns a `409`.

### Request body

- `depth` (string, optional): `standard`, `deep`, or `full`.
- `force` (boolean, optional): `true` skips the failed-check `409`. A forced collection that finds nothing is still refunded.

**cURL request:**

```bash
curl -X POST "https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/posts/collect" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "depth": "deep" }'
```

**Response 202:**

```json
{
  "data": {
    "collection_id": "e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b",
    "status": "processing",
    "depth": "deep",
    "max_videos": 200,
    "credits_reserved": 100
  }
}
```

**Response 409 Running:**

```json
{
  "message": "A collection is already in progress for this creator (collection_id: e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b). Wait for it to complete before starting a new one.",
  "error": "Conflict",
  "statusCode": 409,
  "code": "conflict"
}
```

**Response 409 Unhealthy:**

```json
{
  "statusCode": 409,
  "error": "Tracking unhealthy",
  "message": "This creator's most recent tracking cycle failed (scrape_status=failed, enrichment_status=failed). Post collection reads the same upstream profile, so it would come back empty. No credits were charged.",
  "scrape_status": "failed",
  "enrichment_status": "failed",
  "pause_reason": "Scrape failed 3 consecutive times: TikTok profile response missing user (handle may be deactivated, private, or region-blocked)",
  "last_scraped_at": null,
  "hint": "Fix the handle or resume tracking via PATCH /v1/tracking/creators/:id and wait for a cycle to succeed, or retry with body { \"force\": true } if the failure was transient.",
  "code": "conflict"
}
```

`credits_reserved` is the charge in credits (`100` = $1.00).

---

## Get collection status

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id/posts/collect/:collection_id`

Checks on a post collection. `status` is `processing`, `completed`, or `failed` (with an `error`). `videos_collected` includes videos Virlo already had. If a collection found no posts or failed, `credits_refunded` shows what was paid back (in credits: 50 = $0.50). It appears only when there was a refund. The status is kept for 24 hours, then returns `404`. **Cost:** Free.

**cURL request:**

```bash
curl "https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/posts/collect/e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "data": {
    "collection_id": "e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b",
    "status": "completed",
    "depth": "deep",
    "videos_collected": 200,
    "max_videos": 200,
    "started_at": "2026-09-24T16:02:10.114Z",
    "completed_at": "2026-09-24T16:03:21.580Z"
  }
}
```

---

## Get posting cadence

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id/posting-cadence`

How often a creator posts, counted from stored posts only. **Cost:** Free. TikTok checks mix in old hit videos, so a daily poster can show under 1 post a week, and YouTube counts Shorts only. A `deep` or `full` [collection](https://dev.virlo.ai/docs/tracking#collect-creator-posts) helps, but the real rate is usually higher. Weekdays run from `"0"` (Sunday) to `"6"`, in UTC. Months are 30 days. With no posts, the rates are `null`.

**cURL request:**

```bash
curl "https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/posting-cadence" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "data": {
    "total_posts": 200,
    "avg_days_between_posts": 2.71,
    "posts_per_week": 2.6,
    "posts_per_month": 11.13,
    "day_of_week_distribution": {
      "0": 24, "1": 31, "2": 30, "3": 29,
      "4": 28, "5": 30, "6": 28
    },
    "earliest_post_date": "2025-04-02T16:30:00.000Z",
    "latest_post_date": "2026-09-23T17:40:45.000Z"
  }
}
```

---

## Audience data

Find out who engages with a tracked creator: age, gender, language, country, and city. Virlo builds an **audience snapshot** from the people who comment on recent videos.

- **You ask for it** with [Refresh audience snapshot](https://dev.virlo.ai/docs/tracking#refresh-audience-snapshot). Tracking never makes one, and the creator needs a successful check first.
- **Cost:** $0.50 per new snapshot, charged when it starts. Refunded automatically if the job fails or only has the creator's profile. Reading is free.
- [Demographics](https://dev.virlo.ai/docs/tracking#get-audience-demographics) and [geography](https://dev.virlo.ai/docs/tracking#get-audience-geography) read the same snapshot.

| `data_source` | Meaning |
| - | - |
| `comments` | Commenters. The usual case. |
| `mixed` or `followers` | TikTok, when commenters are few: adds followers, or uses only followers. |
| `comments_extended` | Instagram and YouTube: commenters from more posts. |
| `profile_only` | A guess from the profile. Always `low` confidence, and refunded. |

`confidence_level` is `high` (usually 200 or more people in `sample_size`), `medium` (100 or more), or `low`. `confidence_per_signal` scores each field from 0 to 1.

---

## Refresh audience snapshot

**Endpoint:** `POST https://api.virlo.ai/v1/tracking/creators/:id/audience-refresh`

Gets audience data. A snapshot younger than `freshness_days` comes back free (`source: "cache"`). Otherwise Virlo starts a new one (`source: "fresh"`) and returns a `job_id`. Both return `202`.

**Cost:** $0.50 for a new snapshot. Reusing one is free, but needs a $0.50 balance.

**How long:** 5 to 12 minutes. Poll [the job](https://dev.virlo.ai/docs/tracking#check-audience-refresh-status), or wait for the `audience.snapshot.completed` webhook.

> **Note:** **Repeating this call while a job runs is free.** You get the same `job_id` back, with `credits_used: 0`.

A `409` means no check has succeeded yet, or the last one failed. Wrong handle? Stop tracking and track the right one. PATCH can't fix it, whatever the `hint` says.

### Request body

- `freshness_days` (integer, optional): 0 to 365. Default `30`. `0` always makes a new one.
- `force` (boolean, optional): `true` ignores saved snapshots and skips the `409`. $0.50 each time, unless a job is already running.

**cURL request:**

```bash
curl -X POST "https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/audience-refresh" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "freshness_days": 30 }'
```

**Response 202 New job:**

```json
{
  "data": {
    "source": "fresh",
    "snapshot": null,
    "job_id": "9f8e7d6c-5b4a-4392-8a1b-0c9d8e7f6a5b",
    "status": "processing",
    "credits_used": 50,
    "creator_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "credit_unit": "cent",
    "pending_jobs": [
      {
        "type": "audience_demographics",
        "status": "processing",
        "job_id": "9f8e7d6c-5b4a-4392-8a1b-0c9d8e7f6a5b",
        "poll_url": "/v1/audience/snapshot/9f8e7d6c-5b4a-4392-8a1b-0c9d8e7f6a5b",
        "result_path": "data.snapshot",
        "webhook_event": "audience.snapshot.completed",
        "retry_after_seconds": 15
      }
    ],
    "finalized": false
  }
}
```

**Response 202 Reused:**

```json
{
  "data": {
    "source": "cache",
    "snapshot": {
      "snapshot_id": "f5a3b2c1-d4e5-4f6a-8b7c-9d0e1f2a3b4c",
      "snapshot_at": "2026-09-20T12:04:33.120+00:00",
      "sample_size": 500,
      "age_distribution": { "13-17": 0.14, "18-24": 0.46, "25-34": 0.32, "35-44": 0.06, "45+": 0.02 },
      "gender_distribution": { "male": 0.45, "female": 0.55 },
      "country_distribution": [
        { "pct": 0.22, "code": "US", "name": "US", "confidence": 0.81 },
        { "name": "Other (14 countries)", "pct": 0.78 }
      ],
      "city_distribution": null,
      "language_distribution": { "en": 0.62, "it": 0.18, "pt": 0.09, "es": 0.07, "un": 0.04 },
      "confidence_per_signal": { "age": 0.72, "city": 0.69, "gender": 0.84, "country": 0.79, "language": 0.92 },
      "confidence_level": "high",
      "data_source": "comments",
      "signal_breakdown": { "comments": 500, "followers": 0 },
      "evidence_summary": "Analysis of 500 commenters across 30 recent posts shows...",
      "model_version": "v1"
    },
    "job_id": null,
    "status": null,
    "credits_used": 0,
    "creator_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "credit_unit": "cent",
    "pending_jobs": [],
    "finalized": true
  }
}
```

**Response 409:**

```json
{
  "statusCode": 409,
  "error": "Tracking unhealthy",
  "message": "This creator has not completed a successful tracking cycle yet. Wait for the first cycle to finish (~60-120s after track_creator), then retry audience-refresh.",
  "scrape_status": "failed",
  "enrichment_status": "failed",
  "pause_reason": "Scrape failed 3 consecutive times: TikTok profile response missing user (handle may be deactivated, private, or region-blocked)",
  "last_scraped_at": null,
  "hint": "Wait for the next tracking cycle to succeed, fix the handle via PATCH /v1/tracking/creators/:id, or retry this call with body { \"force\": true } if you believe the failure was transient.",
  "code": "conflict"
}
```

`credits_used` is in credits (`50` = $0.50).

---

## Check audience refresh status

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id/audience-refresh/:jobId`

Checks on an audience job. `status` is `processing`, `completed` (with the snapshot), or `failed`.

**Cost:** Free.

- A failure has `error.code` and `error.message` (`INSUFFICIENT_SAMPLE` means too few people), and is refunded.
- The job is kept for 24 hours, then returns `404`. The snapshot stays readable.
- `pending_jobs[].poll_url` works too. This route has friendlier field names.

**cURL request:**

```bash
curl "https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/audience-refresh/9f8e7d6c-5b4a-4392-8a1b-0c9d8e7f6a5b" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200 Completed:**

```json
{
  "data": {
    "job_id": "9f8e7d6c-5b4a-4392-8a1b-0c9d8e7f6a5b",
    "status": "completed",
    "snapshot": {
      "snapshot_id": "f5a3b2c1-d4e5-4f6a-8b7c-9d0e1f2a3b4c",
      "snapshot_at": "2026-09-24T16:27:09.261+00:00",
      "sample_size": 500,
      "confidence_level": "high",
      "data_source": "comments",
      "model_version": "v1"
    },
    "error": null,
    "pending_jobs": [],
    "finalized": true
  }
}
```

**Response 200 Failed:**

```json
{
  "data": {
    "job_id": "9f8e7d6c-5b4a-4392-8a1b-0c9d8e7f6a5b",
    "status": "failed",
    "snapshot": null,
    "error": {
      "code": "INSUFFICIENT_SAMPLE",
      "message": "Only 12 unique commenters/followers collected after fallback (min 30). Creator likely has very little public engagement or follower visibility. The customer was NOT charged for this attempt."
    },
    "pending_jobs": [],
    "finalized": true
  }
}
```

---

## Get audience demographics

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id/audience-demographics`

Age, gender, and language from the latest audience snapshot. Reading never starts a new one.

**Cost:** Free.

- **You always get the latest snapshot, however old.** `freshness_days` only sets `is_stale: true` when it's older. Without it, `is_stale` is `false`.
- `snapshot` is `null` if there's none. If the first is still running, `pending_jobs[0]` has its `job_id`.
- Language `un` means unknown.

### Query parameters

- `freshness_days` (integer, optional): 0 to 365. Only sets `is_stale`.

**cURL request:**

```bash
curl "https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/audience-demographics?freshness_days=30" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "data": {
    "platform": "tiktok",
    "handle": "khaby.lame",
    "snapshot": {
      "snapshot_id": "f5a3b2c1-d4e5-4f6a-8b7c-9d0e1f2a3b4c",
      "snapshot_at": "2026-09-24T16:27:09.261+00:00",
      "sample_size": 500,
      "age_distribution": { "13-17": 0.14, "18-24": 0.46, "25-34": 0.32, "35-44": 0.06, "45+": 0.02 },
      "gender_distribution": { "male": 0.45, "female": 0.55 },
      "language_distribution": { "en": 0.62, "it": 0.18, "pt": 0.09, "es": 0.07, "un": 0.04 },
      "country_distribution": null,
      "city_distribution": null,
      "confidence_per_signal": { "age": 0.72, "city": 0.69, "gender": 0.84, "country": 0.79, "language": 0.92 },
      "confidence_level": "high",
      "data_source": "comments",
      "signal_breakdown": { "comments": 500, "followers": 0 },
      "evidence_summary": "Analysis of 500 commenters across 30 recent posts shows...",
      "model_version": "v1"
    },
    "is_stale": false,
    "pending_jobs": [],
    "finalized": true
  }
}
```

**Response 200 In progress:**

```json
{
  "data": {
    "platform": "tiktok",
    "handle": "khaby.lame",
    "snapshot": null,
    "is_stale": false,
    "pending_jobs": [
      {
        "type": "audience_demographics",
        "status": "processing",
        "job_id": "9f8e7d6c-5b4a-4392-8a1b-0c9d8e7f6a5b",
        "poll_url": "/v1/audience/snapshot/9f8e7d6c-5b4a-4392-8a1b-0c9d8e7f6a5b",
        "result_path": "data.snapshot",
        "webhook_event": "audience.snapshot.completed",
        "retry_after_seconds": 15
      }
    ],
    "finalized": false
  }
}
```

---

## Get audience geography

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/creators/:id/audience-geography`

Countries and cities, from the same snapshot as [demographics](https://dev.virlo.ai/docs/tracking#get-audience-demographics) and with the same rules.

**Cost:** Free.

- A country's `name` repeats its `code`, such as `US`. Small countries roll up into `Other (N countries)`.
- A city's `name` is the city. `city_distribution` can be `null`.

### Query parameters

- `freshness_days` (integer, optional): 0 to 365. Only sets `is_stale`.

**cURL request:**

```bash
curl "https://api.virlo.ai/v1/tracking/creators/a1b2c3d4-e5f6-7890-abcd-ef1234567890/audience-geography" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "data": {
    "platform": "tiktok",
    "handle": "khaby.lame",
    "snapshot": {
      "snapshot_id": "f5a3b2c1-d4e5-4f6a-8b7c-9d0e1f2a3b4c",
      "snapshot_at": "2026-09-24T16:27:09.261+00:00",
      "sample_size": 500,
      "age_distribution": null,
      "gender_distribution": null,
      "language_distribution": null,
      "country_distribution": [
        { "pct": 0.22, "code": "US", "name": "US", "confidence": 0.81 },
        { "pct": 0.19, "code": "IT", "name": "IT", "confidence": 0.84 },
        { "name": "Other (14 countries)", "pct": 0.59 }
      ],
      "city_distribution": [
        { "pct": 0.08, "code": "IT", "name": "Milan", "confidence": 0.71 }
      ],
      "confidence_level": "high",
      "data_source": "comments",
      "model_version": "v1"
    },
    "is_stale": false,
    "pending_jobs": [],
    "finalized": true
  }
}
```

---

## Track a video

**Endpoint:** `POST https://api.virlo.ai/v1/tracking/videos`

Starts tracking one video and runs the first check right away.

**Cost:** $0.25. The link isn't checked first. If the video can't be read, you're refunded about 40 seconds later, and it stays `paused` with `enrichment_status: "failed"`.

**How long:** usually under a minute. Poll [Get tracked video](https://dev.virlo.ai/docs/tracking#get-tracked-video).

Videos follow the same once-per-account rule, with `video_id` in the `409`. A video you [stopped](https://dev.virlo.ai/docs/tracking#stop-tracking-video) returns `409` too. `400 Failed to create tracked video` means the `tracking_account_id` isn't a creator you track, or you sent two YouTube `/shorts/` links seconds apart.

### Request body

- `url` (string, required): TikTok: `https://www.tiktok.com/@user/video/ID`. YouTube: `https://www.youtube.com/watch?v=ID` or `https://youtu.be/ID` (best), or `/shorts/ID`. Instagram: `https://www.instagram.com/reel/CODE/` or `/p/CODE/`.
- `platform` (string, required): `tiktok`, `youtube`, or `instagram`.
- `scrape_cadence` (string, optional): As in [Track a creator](https://dev.virlo.ai/docs/tracking#track-a-creator).
- `tracking_account_id` (string, optional): A tracked creator's `id`, if it's their video, so the report can weigh views against their followers. Can't change later.

**cURL request:**

```bash
curl -X POST https://api.virlo.ai/v1/tracking/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://www.tiktok.com/@khaby.lame/video/7678009073421405471",
    "platform": "tiktok",
    "scrape_cadence": "daily"
  }'
```

**JavaScript request:**

```js
const response = await fetch(
  'https://api.virlo.ai/v1/tracking/videos',
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: 'https://www.tiktok.com/@khaby.lame/video/7678009073421405471',
      platform: 'tiktok',
      scrape_cadence: 'daily'
    })
  }
);
const data = await response.json();
```

**Python request:**

```python
import requests

response = requests.post(
    'https://api.virlo.ai/v1/tracking/videos',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    json={
        'url': 'https://www.tiktok.com/@khaby.lame/video/7678009073421405471',
        'platform': 'tiktok',
        'scrape_cadence': 'daily'
    }
)
data = response.json()
```

**Response 202:**

```json
{
  "data": {
    "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "status": "active",
    "message": "Video tracking started. Initial metrics and AI report are being generated."
  }
}
```

**Response 409 Already tracked:**

```json
{
  "statusCode": 409,
  "error": "Conflict",
  "message": "You're already tracking this video.",
  "video_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "code": "conflict"
}
```

**Response 402:**

```json
{
  "statusCode": 402,
  "message": "Insufficient balance. $0.25 required, $0.12 remaining. Add funds at https://dev.virlo.ai/dashboard/billing",
  "error": "Payment Required",
  "required_credits": 25,
  "remaining_credits": 12,
  "required_amount": "$0.25",
  "remaining_balance": "$0.12",
  "code": "insufficient_credits"
}
```

---

## List tracked videos

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/videos`

Works like [List tracked creators](https://dev.virlo.ai/docs/tracking#list-tracked-creators), with the same `platform`, `page`, and `limit`. Each item includes the whole AI report, so items are large.

**Cost:** Free.

### Query parameters

- `search` (string, optional): Part of the video's title or link.

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/tracking/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d limit=20
```

**Response 200:**

```json
{
  "data": [
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "platform": "tiktok",
      "url": "https://www.tiktok.com/@khaby.lame/video/7678009073421405471",
      "status": "active",
      "enrichment_status": "ready",
      "latest_views": 101400000,
      "growth_rate": 0.0091,
      "next_scrape_at": "2026-09-25T14:00:04.723+00:00"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "total_pages": 1,
    "has_next_page": false,
    "has_prev_page": false
  }
}
```

---

## Get tracked video

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/videos/:id`

One tracked video: numbers, settings, transcript, and latest AI report.

**Cost:** Free.

**Full field reference**

Every field is in the example, and there's no `updated_at`. Notes:

- `growth_rate`: view change since the last check. `0.009` means up 0.9% per check, not per day. It reads `0` after the first check.
- `latest_shares` and `latest_bookmarks`: TikTok only, `0` elsewhere.
- `duration`: often `null` on YouTube.
- `platform_video_id`: empty for a YouTube `/shorts/` link until the first check.
- `analysis`: the [report](https://dev.virlo.ai/docs/tracking#get-video-report), `null` until the first check.

**cURL request:**

```bash
curl https://api.virlo.ai/v1/tracking/videos/b2c3d4e5-f6a7-8901-bcde-f12345678901 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "data": {
    "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "platform": "tiktok",
    "platform_video_id": "7678009073421405471",
    "url": "https://www.tiktok.com/@khaby.lame/video/7678009073421405471",
    "title": "They were there yesterday, I promise #learnfromkhaby #comedy",
    "description": "They were there yesterday, I promise #learnfromkhaby #comedy",
    "thumbnail_url": "https://p16-common-sign.tiktokcdn-us.com/...",
    "duration": 21,
    "published_at": "2026-08-25T16:36:58+00:00",
    "author_handle": "khaby.lame",
    "author_name": "Khabane lame",
    "author_avatar_url": "https://p19-common-sign.tiktokcdn-us.com/...",
    "status": "active",
    "scrape_cadence": "daily",
    "enrichment_status": "ready",
    "latest_views": 101400000,
    "latest_likes": 6200000,
    "latest_comments": 31000,
    "latest_shares": 450000,
    "latest_bookmarks": 120000,
    "latest_transcript": null,
    "growth_rate": 0.0091,
    "analysis": {
      "key_insight": "A relatable mix-up with a silent payoff..."
    },
    "analysis_updated_at": "2026-09-24T14:00:31.026+00:00",
    "tracking_account_id": null,
    "last_scraped_at": "2026-09-24T14:00:04.723+00:00",
    "next_scrape_at": "2026-09-25T14:00:04.723+00:00",
    "created_at": "2026-09-20T10:30:00.000+00:00"
  }
}
```

**Response 404:**

```json
{
  "message": "Tracked video not found",
  "error": "Not Found",
  "statusCode": 404,
  "code": "not_found"
}
```

---

## Get video report

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/videos/:id/report`

The latest AI report on the video: how it's doing, why it works, the hook, and how commenters react. `analysis` is `null` until the first check. **Cost:** Free. `video` is the full tracked video, `analysis` included. `overview.engagement_rate` is a percent: `7.06` means 7.06%.

**cURL request:**

```bash
curl https://api.virlo.ai/v1/tracking/videos/b2c3d4e5-f6a7-8901-bcde-f12345678901/report \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "data": {
    "video": {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "platform": "tiktok",
      "url": "https://www.tiktok.com/@khaby.lame/video/7678009073421405471",
      "latest_views": 101400000
    },
    "analysis": {
      "overview": {
        "summary": "A silent comedy bit about a missing object...",
        "outlier_ratio": null,
        "growth_insight": "Views are still climbing a month after posting...",
        "engagement_rate": 7.06,
        "growth_trajectory": "steady",
        "performance_verdict": "overperforming"
      },
      "key_insight": "A relatable mix-up with a silent payoff...",
      "what_works": [
        { "insight": "The punchline lands without a single word", "evidence": "Top comments come in many languages..." }
      ],
      "discoveries": [
        { "text": "Saves are unusually high for a comedy clip...", "metric": "1.2% save rate" }
      ],
      "hook_analysis": {
        "hook_type": "Pattern interrupt",
        "description": "Opens mid-problem, so viewers stay to see the fix...",
        "effectiveness": "strong"
      },
      "content_breakdown": {
        "topics": ["Everyday mishaps"],
        "format_style": "Silent reaction with one visual punchline...",
        "target_audience": "A global audience of all ages..."
      },
      "audience_sentiment": {
        "overall_summary": "Warm and amused...",
        "sentiment_labels": ["appreciative", "amused"],
        "notable_comments": [
          { "likes": 52000, "content": "This is literally me every morning", "insight": "Viewers see themselves in the joke" }
        ],
        "key_findings": [
          { "finding": "Viewers tag friends who do the same thing...", "takeaway": "Relatable mistakes drive shares..." }
        ]
      }
    },
    "analysis_updated_at": "2026-09-24T14:00:31.026+00:00"
  }
}
```

---

## Get video snapshots

**Endpoint:** `GET https://api.virlo.ai/v1/tracking/videos/:id/snapshots`

The video's views, likes, comments, shares, and saves at each check. **Cost:** Free. Works like [creator snapshots](https://dev.virlo.ai/docs/tracking#get-creator-snapshots), with the same `start_date`, `end_date`, and `limit`: the most recent checks, listed oldest first. `shares` and `bookmarks` are `0` on YouTube and Instagram.

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/tracking/videos/b2c3d4e5-f6a7-8901-bcde-f12345678901/snapshots \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d start_date=2026-09-17T00:00:00Z
```

**Response 200:**

```json
{
  "data": [
    {
      "id": "8734c9e6-efce-4c49-8262-2e20ac8f204f",
      "tracking_video_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "views": 101400000,
      "likes": 6200000,
      "comments": 31000,
      "shares": 450000,
      "bookmarks": 120000,
      "snapshot_at": "2026-09-24T14:00:04.723+00:00",
      "delta_views": 920000,
      "delta_likes": 20000,
      "delta_comments": 200,
      "delta_shares": 2000,
      "delta_bookmarks": 1000
    }
  ]
}
```

---

## Update tracked video

**Endpoint:** `PATCH https://api.virlo.ai/v1/tracking/videos/:id`

Works like [Update tracked creator](https://dev.virlo.ai/docs/tracking#update-tracked-creator), with the same `status` and `scrape_cadence` fields and the same resume rules. The link and `tracking_account_id` can't change. The response is the full video. **Cost:** Free, but resuming triggers a $0.25 check.

**cURL request:**

```bash
curl -X PATCH https://api.virlo.ai/v1/tracking/videos/b2c3d4e5-f6a7-8901-bcde-f12345678901 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "paused" }'
```

**Response 200:**

```json
{
  "data": {
    "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "platform": "tiktok",
    "url": "https://www.tiktok.com/@khaby.lame/video/7678009073421405471",
    "status": "paused",
    "scrape_cadence": "daily",
    "next_scrape_at": "2026-09-25T14:00:04.723+00:00"
  }
}
```

---

## Stop tracking video

**Endpoint:** `DELETE https://api.virlo.ai/v1/tracking/videos/:id`

Stops checks and charges, and hides the video from your list. Its data stays readable by `id`, with `status: "deleted"`. A second DELETE also returns `204`.

**Cost:** Free.

> **Note:** **A stopped video can't be tracked again.** The only way back is to [update](https://dev.virlo.ai/docs/tracking#update-tracked-video) the old `id` to `paused` or `active`. To take a break, pause instead.

**cURL request:**

```bash
curl -X DELETE https://api.virlo.ai/v1/tracking/videos/b2c3d4e5-f6a7-8901-bcde-f12345678901 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 204 No Content:**

```text
(empty response body)
```

---

## Webhook notifications

To get a message on your server instead of polling, subscribe with the [Webhooks API](https://dev.virlo.ai/docs/webhooks#create-webhook) (`POST /v1/webhooks`). Only items tracked through the API send these.

| Event | Sent when |
| - | - |
| `tracking.cycle.completed` | A check finished, and its report is ready. |
| `tracking.outlier_video.detected` | A creator has a new breakout video. |
| `tracking.paused` | 3 attempts failed, or your balance can't cover a check. |
| `audience.snapshot.completed` | An audience snapshot is ready. Not sent on failure, so also [check the job](https://dev.virlo.ai/docs/tracking#check-audience-refresh-status). |

After a low-balance pause, add funds, then set `status` to `active`. If Virlo couldn't reach the creator or video, the handle or link can't be edited: stop tracking and track the right one. Message fields: [Tracking updates](https://dev.virlo.ai/docs/webhooks#tracking-payload).

---

## Workflow recipes

### Vet a creator before a paid deal

1. [Track the creator](https://dev.virlo.ai/docs/tracking#track-a-creator) and wait for `ready`: $0.25.
2. [Refresh the audience snapshot](https://dev.virlo.ai/docs/tracking#refresh-audience-snapshot): $0.50.
3. Read the [report](https://dev.virlo.ai/docs/tracking#get-creator-report), [demographics](https://dev.virlo.ai/docs/tracking#get-audience-demographics), and [geography](https://dev.virlo.ai/docs/tracking#get-audience-geography), free.
4. [Stop tracking](https://dev.virlo.ai/docs/tracking#stop-tracking-creator) so no more checks are charged.

Total: about $0.75.

---

## Errors

Errors are never charged. For the full list, see [Errors](https://dev.virlo.ai/docs/errors).

| Status | Usually means |
| - | - |
| `400` | A missing, misspelled, or unknown field, or a value like `TikTok` (use lowercase). |
| `402` | Your balance is too low. Read `code` and `required_credits`. |
| `404` | No item with that ID for your team. Use Virlo's `id`, not the platform's video ID. |
| `409` | Already tracked on your account, a post collection is already running, or a post collection or audience refresh is blocked by a failed check. |

