# Creator lookup

> Look up any creator on TikTok, YouTube Shorts, or Instagram. Start a lookup, get a job ID, then fetch their profile, stats, top hashtags, standout videos, and optional audience, trend analysis, and Data Intelligence add-ons.

Source: https://dev.virlo.ai/docs/satellite
Markdown: https://dev.virlo.ai/docs/satellite.md
Section: Deep-dive lookups

## 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.

---

See how any creator on TikTok, YouTube Shorts, or Instagram performs: their stats, top hashtags, standout videos, and who comments on their posts.

**At a glance**

- **What it does:** Sums up one creator (or up to 25 at once): profile, stats, top hashtags, and the videos that beat their usual views.
- **You send:** A platform (`tiktok`, `youtube`, or `instagram`), the creator's handle, and any add-ons you want.
- **You get back:** A job ID right away. You check that job every 15 seconds or so (this is called polling) until the result is ready.
- **Cost:** $0.50 per creator, up to $1.75 with every add-on. Repeating it within 6 hours is free if the earlier lookup had every option you ask for.
- **How long:** Usually 20 to 40 seconds. Bigger lookups and add-ons can take a few minutes.

> **Note:** Every address here starts with `https://api.virlo.ai/v1/satellite/`. Successful answers sit inside a `data` field (the job ID is `data.job_id`). Error messages don't.

---

## What it costs

You pay when you start a lookup. Checking status and re-reading saved results are free.

| What you ask for | Price |
| - | - |
| Creator lookup | $0.50 |
| Trend analysis (`trend_analysis=true`) | + $0.50 |
| Data Intelligence, an AI breakdown of every video (`data_intelligence=true`) | + $0.25 |
| Audience breakdown (`audience_demographics` and/or `audience_geography`) | + $0.50 once, even for both. Free if Virlo measured this creator's audience in the last 30 days. |
| Every add-on at once | Up to $1.75 |
| The same creator again within 6 hours | Free if the earlier lookup had every option you ask for. Batches too. |
| Batch lookup | $0.50 per creator that starts a lookup, plus any audience fees |

- **Failed lookups are refunded in full**, such as a handle that doesn't exist, a private account, a YouTube channel with no Shorts, or a lookup that stalls and is closed out as failed. The trend fee is refunded when no trends come out, the audience fee when no new snapshot was needed, and a creator lookup's Data Intelligence fee when there were no posts to enrich or its analysis failed. The refund comes later, as [`credits_refunded`](https://dev.virlo.ai/docs/satellite#poll-lookup-status) on the status check and on the [saved run](https://dev.virlo.ai/docs/satellite#durable-runs).
- **The exact charge is in the response headers:** `X-Cost` in dollars and `X-Credits-Used` in credits (1 credit = $0.01, so $0.50 = 50 credits).

---

## Start a creator lookup

**Endpoint:** `GET https://api.virlo.ai/v1/satellite/creator/:platform/:username`

Starts a lookup and returns a job ID in about a second, while the work runs in the background. Each new lookup is charged, even though it's a `GET` request.

Cost:
$0.50, up to $1.75 with every add-on. Free if repeated within 6 hours.

**Repeated within 6 hours by anyone on your team?** You get the same job ID back, already `completed` and marked `"cached": true`. This only happens when the earlier lookup had every add-on and include you ask for, at least as many videos (`max_videos`), and the same `outlier_threshold`. Otherwise a new paid lookup runs. A cached repeat costs nothing, but your balance must still cover the normal price.

### Path parameters

- `platform` (string, required): `tiktok`, `youtube`, or `instagram`, in lowercase. YouTube reads Shorts only, so a channel with only long videos fails (and is refunded). Instagram reads the whole feed, but photos and carousels have no views, so view stats and outliers use only the videos.
- `username` (string, required): The handle, with or without the `@`. It can't contain `/`, `?`, `#`, `%`, or spaces.

### Query parameters

All optional. To turn an option on, send lowercase `true`. `True`, `1`, or `yes` count as off, with no error. A misspelled option name returns `400` and isn't billed.

- `include` (string, optional): `videos` (the video list), `outliers` (videos that beat the creator's usual views), or both, comma-separated. Without it you get the profile, stats, and hashtags.
- `max_videos` (integer, optional): How many recent videos to analyze, from 1 to 100 (default `20`). It doesn't change the price.
- `outlier_threshold` (number, optional): A video is an outlier when its views are more than this many times the creator's median views. Default `2` (double), minimum `0.1`. Use `3` or `5` to see only the biggest hits.
- `cross_links` (boolean, optional): Find the creator's profiles on other platforms (YouTube, TikTok, Instagram, X, Spotify). Free.
- `trend_analysis` (boolean, optional): **+$0.50.** The formats the creator keeps repeating. Reads up to 300 videos and ignores `max_videos`. Includes the video list. See [Creator trend analysis](https://dev.virlo.ai/docs/satellite#creator-trend-analysis).
- `data_intelligence` (boolean, optional): **+$0.25.** An AI breakdown of every video, plus a "what's working" summary. Includes the video list. The results land on the [saved run](https://dev.virlo.ai/docs/satellite#durable-runs) a few minutes after the lookup. See [Data Intelligence](https://dev.virlo.ai/docs/satellite/creators#data-intelligence) for an example.
- `context` (string, optional): Only with `data_intelligence`. Up to 600 characters on what you want to learn. The summary focuses on it.
- `slideshow_sort` (string, optional): Only with `data_intelligence`. Virlo reads the slide text of up to 100 slideshows. `popular` (default) or `recent` picks which ones.
- `audience_demographics` (boolean, optional): **+$0.50 for a new snapshot.** Age, gender, and language of the people who comment, not every follower. Refunded if no new snapshot was needed, or if it fails or is a profile-only guess.
- `audience_geography` (boolean, optional): Countries and cities of the same commenters. Uses the same snapshot and fee as `audience_demographics`.
- `freshness_days` (integer, optional): How old (0 to 365 days, default `30`) a saved audience snapshot can be before Virlo makes a new one.

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/satellite/creator/tiktok/khaby.lame \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d include=videos,outliers \
  -d cross_links=true \
  -d max_videos=30 \
  -d outlier_threshold=2.5
```

**JavaScript request:**

```js
const params = new URLSearchParams({
  include: 'videos,outliers',
  cross_links: 'true',
  max_videos: '30',
  outlier_threshold: '2.5'
});

const response = await fetch(
  `https://api.virlo.ai/v1/satellite/creator/tiktok/khaby.lame?${params}`,
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const { data } = await response.json();
// data.job_id → use it to check the status
```

**Python request:**

```python
import requests

params = {
    'include': 'videos,outliers',
    'cross_links': 'true',  # a string: Python's True is sent as "True", which counts as off
    'max_videos': 30,
    'outlier_threshold': 2.5
}

response = requests.get(
    'https://api.virlo.ai/v1/satellite/creator/tiktok/khaby.lame',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    params=params
)
data = response.json()['data']
# data['job_id'] → use it to check the status
```

**Response 200 Started:**

```json
{
  "data": {
    "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "processing"
  }
}
```

**Response 200 Cached:**

```json
{
  "data": {
    "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "completed",
    "cached": true
  }
}
```

**Response 402:**

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

**Response 429:**

```json
{
  "statusCode": 429,
  "code": "rate_limit_exceeded",
  "message": "Rate limit exceeded",
  "error": "Too Many Requests",
  "limit": 5,
  "remaining": 0,
  "reset_at": 1790270520,
  "reset_at_formatted": "September 24th, 2026, 5:22 PM UTC",
  "retry_after": 15,
  "resetAt": 1790270520,
  "resetAtFormatted": "September 24th, 2026, 5:22 PM UTC",
  "retryAfter": 15
}
```

---

## Creator trend analysis

Add `trend_analysis=true` to see the formats a creator keeps repeating, when they ran, and whether each is growing or fading. It adds a `trends` object to the result. Without it, there's no `trends` key.

- **`summary`** sums up the creator's playbook in a sentence or two.
- **`result.trends.trends`** lists each format: `name`, `description`, `tactics`, `confidence` (0 to 1), `video_count`, `evidence_video_ids` (matching `id` in `videos`), and `time_windows` (the date ranges it ran, with view and engagement totals).
- **`resurged`** is `true` when the creator dropped a format and brought it back.
- **`momentum`** compares average views in the latest date range with the one before: `stronger` (15% or more up), `weaker` (15% or more down), or `similar`. It's `null` if the format ran only once.
- **`status`** is `ok`, or `insufficient_corpus` when fewer than 10 videos could be read. The trend fee is refunded for `insufficient_corpus`.
- **An empty list with `status: "ok"`** means no clear pattern. If `model_used` is also `null`, the analysis failed and the trend fee is refunded. You can run it again after the 6-hour window.
- **Ignore** `analyzed`, `cost_usd`, and `tokens_used`. They describe Virlo's own AI cost, not your bill.

Full example: [Creator lookup add-ons](https://dev.virlo.ai/docs/satellite/creators).

---

## Check lookup status

**Endpoint:** `GET https://api.virlo.ai/v1/satellite/creator/status/:job_id`

Shows the lookup's progress, then the full result once it's done. Check every 15 seconds, or whatever `retry_after_seconds` says. It's free.

### When is it done?

- **Basic lookup:** stop when `status` is `completed` or `failed`.
- **With an audience add-on:** stop when `finalized` is `true`. A failed snapshot never makes it `true`, so if an audience field still reads `processing` after 15 minutes, check its job at `pending_jobs[].poll_url`. It ends `completed` or `failed`, and a failed snapshot is refunded.
- **With `data_intelligence`:** the AI breakdown isn't in this response. After `completed`, check the [saved run](https://dev.virlo.ai/docs/satellite#durable-runs) until `intelligence_status` is `ready`, `failed`, or `skipped`, usually a few minutes.

Most take 20 to 40 seconds, more with a high `max_videos`. Trend analysis and busy periods can take a few minutes, so don't hard-code a timeout. `status` still `processing` after 15 minutes? It's delayed, not lost. A stuck lookup is marked `failed` on its saved run within a few hours, with no automatic refund. For help sooner, email <info@virlo.ai> with the job ID.

This check works for 24 hours, then returns `404`. The result stays in the [saved run](https://dev.virlo.ai/docs/satellite#durable-runs) under the same ID.

### Path parameters

- `job_id` (string, required): The job ID from the start call.

### What you get back

- `status` (string, optional): `processing`, `completed`, or `failed`.
- `result` (object, optional): The creator data, once `completed`.
- `error` (string, optional): Why the lookup failed, if it did.
- `credits_refunded` (integer, optional): Only on a failed lookup that was refunded: the credits paid back (1 credit = $0.01).
- `pending_jobs` (array, optional): Work still running after the main result: here, only a new audience snapshot. See [pending_jobs](https://dev.virlo.ai/docs/async-data#pending-jobs).
- `finalized` (boolean, optional): `true` once the lookup and any audience snapshot are done. A failed snapshot keeps it `false` (see above).

### What's in `result`

- `username, platform` (string, optional): What you looked up.
- `profile` (object, optional): Followers, bio, avatar, profile link, and content category. Missing if it couldn't be fetched.
- `stats` (object, optional): Averages across the analyzed videos: views, median views, likes, comments, engagement rate, and how often they post.
- `hashtags` (object[], optional): The top 10 hashtags by total views.
- `content_tags` (string[], optional): 2 to 5 AI-written tags for the creator's niche, like `["asmr", "budget-meals"]`.
- `videos` (object[], optional): The analyzed videos. With `include=videos`, `trend_analysis`, or `data_intelligence`.
- `outliers` (object, optional): Videos that beat the creator's own median views. With `include=outliers`.
- `cross_links` (object, optional): The creator's profiles on other platforms. With `cross_links=true`.
- `audience_demographics, audience_geography` (object, optional): The audience snapshot, or `{ "status": "processing", "job_id": "..." }` while a new one is made. It appears here once ready. You can also get a [webhook](https://dev.virlo.ai/docs/webhooks) for `audience.snapshot.completed`.
- `trends` (object, optional): Only with `trend_analysis=true`. See [Creator trend analysis](https://dev.virlo.ai/docs/satellite#creator-trend-analysis).
- `run_id` (string, optional): Same as your job ID. Use it to [re-read the result](https://dev.virlo.ai/docs/satellite#durable-runs) for free.
- `credits_charged` (number, optional): Not for billing. It counts only the base price and trend fee, not the Data Intelligence or audience fees. Your real charge is the `X-Credits-Used` header on the start call.
- `credits_refunded` (integer, optional): Only when a trend or audience fee bought nothing: the credits paid back. `X-Cost` still shows the full charge.

**Full field reference**

Some fields exist only on some platforms. A missing value can come back as `null`, as `0`, or not at all, so handle all three.

### profile

- `followers` (number, optional): Followers or subscribers.
- `following` (number, optional): Accounts they follow. Always `0` on YouTube.
- `avatar_url, url, description` (string | null, optional): Profile picture, profile link, and bio.
- `is_verified` (boolean | null, optional): Verified badge. Often `null` on YouTube.
- `total_videos, total_posts` (number | null, optional): Totals on the account, when the platform reports them. On TikTok they're equal.
- `total_likes` (number | null, optional): TikTok only.
- `total_views` (number | null, optional): YouTube only.
- `category` (string | null, optional): Content category picked by AI, like `comedy`. `null` if unclear.
- `bio_link` (string | null, optional): TikTok and Instagram only. The link in their bio.
- `country` (string | null, optional): TikTok only. A two-letter code like `US`, or `null`.
- `attribution` (string | null, optional): YouTube only. The link text on the channel header.

### stats

- `videos_analyzed` (number, optional): Posts with a view count that went into the view math.
- `posts_analyzed` (number, optional): All posts analyzed. Differs only on Instagram, where photos and carousels have no views.
- `post_mix` (object, optional): Instagram mixed feeds only: how many `video`, `carousel`, and `image` posts were analyzed.
- `avg_views, median_views` (number, optional): Average and median (middle) views per video.
- `avg_likes, avg_comments` (number, optional): Average likes and comments per post.
- `engagement_rate` (number, optional): Likes plus comments, divided by views, rounded to 2 decimals (`0.07` = 7%). Don't compare it across platforms.
- `top_performing_views, lowest_performing_views` (number, optional): Highest and lowest views among the analyzed videos.
- `posting_frequency_days` (number, optional): Average days between posts. Needs at least 2 dated posts.

### hashtags

Each entry has `hashtag` (lowercase, no `#`), `total_views`, `avg_views`, `avg_likes`, `avg_comments`, and `used_count`. They come from titles and descriptions.

### videos

Each video has `id`, `title`, `description`, `views`, `likes`, `comments`, `publish_date`, `url`, `hashtags`, `thumbnail_url`, `is_duet`, and `is_stitch` (always `false` off TikTok). Some keys only exist on some platforms:

- `shares`, `bookmarks`, and `is_pinned`: TikTok only.
- `duration` (seconds): TikTok and Instagram. Missing on YouTube.
- `is_video` and `content_type` (`reel`, `video`, `carousel`, or `image`): Instagram only. Photos and carousels show `views: 0`.
- `publishDate` and `isPinned` are old duplicates. Use `publish_date` and `is_pinned`.

### outliers

`threshold_multiplier` is the threshold used. `outlier_videos` lists every video above it, sorted by `weighted_score`, highest first. Each has all the video fields, plus:

- `outlier_ratio` (number, optional): Views divided by the creator's median views. `6.56` means 6.56 times their usual.
- `weighted_score` (number, optional): `ln(outlier_ratio) × ln(median_views)`, always present. The same jump scores higher for a creator with bigger usual views, so use it to compare across creators.

### cross_links

- `discovered` (object[], optional): Confident matches only, so it can be empty. Each is `{ platform, username, url, source }`. `platform` is `youtube`, `tiktok`, `instagram`, `twitter`, or `spotify`. `source` is where it was found: `bio_link`, `bio_text`, `linktree`, `spotify_api`, or `ai_search`.
- `sources_checked` (string[], optional): Where Virlo searched. Includes `timeout` if the search hit its 8-second limit. You still get the links found so far.
- `bio_link_url` (string, optional): The link-in-bio page checked, if any. May be missing.

### Audience snapshot

Both audience fields read one snapshot, built from the creator's commenters (on TikTok, followers too when comments are thin). Each has `snapshot_id`, `sample_size` (people analyzed), `confidence_level` (`low`, `medium`, or `high`), `confidence_per_signal`, `data_source`, `signal_breakdown`, and `model_version`.

- **`audience_demographics`** adds `snapshot_at`, `evidence_summary` (a short written summary), and `age_distribution`, `gender_distribution`, and `language_distribution`. Shares add up to about 1 (`0.46` = 46%).
- **`audience_geography`** adds `country_distribution` and `city_distribution`: lists of `{ code, name, pct, confidence }`, largest first. The last entry groups places with only one person, `{ name: "Other (N countries)", pct }` (or `Other (N cities)`), with no `code` or `confidence`.
- **`data_source: "profile_only"`** is a guess from the creator's own profile: always low confidence, no age or gender, and always refunded. See [Audience data](https://dev.virlo.ai/docs/tracking#audience-basics) for every source.

**cURL request:**

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

**JavaScript request:**

```js
const jobId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';

// Check every 15 seconds until the lookup is done
let data;
while (true) {
  const response = await fetch(
    `https://api.virlo.ai/v1/satellite/creator/status/${jobId}`,
    { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
  );
  ({ data } = await response.json());
  if (data.status === 'completed' || data.status === 'failed') break;
  await new Promise((resolve) => setTimeout(resolve, 15_000));
}
```

**Python request:**

```python
import time
import requests

job_id = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'

# Check every 15 seconds until the lookup is done
while True:
    response = requests.get(
        f'https://api.virlo.ai/v1/satellite/creator/status/{job_id}',
        headers={'Authorization': 'Bearer YOUR_API_KEY'}
    )
    data = response.json()['data']
    if data['status'] in ('completed', 'failed'):
        break
    time.sleep(15)
```

**Response Completed:**

```json
{
  "data": {
    "status": "completed",
    "result": {
      "username": "khaby.lame",
      "platform": "tiktok",
      "stats": {
        "videos_analyzed": 30,
        "posts_analyzed": 30,
        "avg_views": 45000000,
        "median_views": 32000000,
        "avg_likes": 3200000,
        "avg_comments": 18000,
        "engagement_rate": 0.07,
        "top_performing_views": 210000000,
        "lowest_performing_views": 5000000,
        "posting_frequency_days": 3.2
      },
      "hashtags": [
        { "hashtag": "comedy", "total_views": 890000000, "avg_views": 59333333, "avg_likes": 4100000, "avg_comments": 22000, "used_count": 15 }
      ],
      "content_tags": ["silent-comedy", "life-hacks", "reaction"],
      "profile": {
        "followers": 162400000,
        "following": 78,
        "url": "https://www.tiktok.com/@khaby.lame",
        "is_verified": true,
        "total_videos": 1203,
        "total_posts": 1203,
        "total_likes": 2400000000,
        "category": "comedy",
        "bio_link": null,
        "country": null
      },
      "videos": [
        {
          "id": "7234567890123456789",
          "title": "When people overcomplicate things...",
          "views": 85000000,
          "likes": 6200000,
          "comments": 31000,
          "shares": 120000,
          "bookmarks": 95000,
          "duration": 21,
          "publish_date": "2025-03-15T12:00:00.000Z",
          "url": "https://www.tiktok.com/@khaby.lame/video/7234567890123456789",
          "hashtags": ["comedy", "reaction"],
          "is_pinned": false,
          "is_duet": false,
          "is_stitch": true
        }
      ],
      "outliers": {
        "threshold_multiplier": 2.5,
        "outlier_videos": [
          {
            "id": "7229876543210987654",
            "title": "Why make it hard?",
            "views": 210000000,
            "url": "https://www.tiktok.com/@khaby.lame/video/7229876543210987654",
            "outlier_ratio": 6.56,
            "weighted_score": 32.51
          }
        ]
      },
      "cross_links": {
        "discovered": [
          { "platform": "youtube", "username": "khaby.lame", "url": "https://www.youtube.com/@khaby.lame", "source": "bio_link" }
        ],
        "sources_checked": ["structured_fields", "bio_text", "linktree", "spotify_api", "ai_search"]
      },
      "run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "credits_charged": 50
    },
    "pending_jobs": [],
    "finalized": true
  }
}
```

**Response Processing:**

```json
{
  "data": {
    "status": "processing",
    "pending_jobs": [],
    "finalized": false
  }
}
```

**Response Failed:**

```json
{
  "data": {
    "status": "failed",
    "error": "No posts found for @username on tiktok. The account may not exist, be private, or have no public content.",
    "credits_refunded": 50,
    "pending_jobs": [],
    "finalized": true
  }
}
```

**Response 404:**

```json
{
  "message": "Creator lookup job not found or expired",
  "error": "Not Found",
  "statusCode": 404,
  "code": "not_found"
}
```

---

## Re-read past runs

Every lookup is saved as a "run" that you can read again for free, with no 24-hour limit. The run ID is the job ID you got at the start. Sound lookups, hashtag lookups, and [video outlier checks](https://dev.virlo.ai/docs/satellite/video-outlier) are saved the same way. A run shows the numbers from when it ran. For fresh numbers, start a new lookup, which is billed.

> **Note:** **Every paid lookup is saved as its own run, with a new run ID.** Older runs are never overwritten. A repeat within the 6-hour free window returns the existing run instead.

### Get one run

`GET /v1/satellite/runs/:run_id` returns the saved run.

**Full field reference**

- `run_id` (string, optional): Same as the job ID.
- `type` (string, optional): `creator_lookup`, `sound_lookup`, `hashtag_lookup`, or `video_outlier`.
- `platform, status` (string, optional): The platform, and `processing`, `completed`, or `failed`.
- `subject` (string | null, optional): The handle, sound ID, or hashtag. `null` for video outliers.
- `created_at` (string, optional): When the run was created.
- `completed_at` (string | null, optional): When it finished. Use it to judge how fresh the data is.
- `credits_used` (number | null, optional): Currently always `null`. The real charge is the `X-Credits-Used` header on the call that started the run.
- `request` (object, optional): The options the lookup used, such as `data_intelligence`.
- `result` (object | null, optional): The same `result` the status check returns. With `data_intelligence`, each video (and outlier) also has an `intelligence` object with 43 fields (35 for slideshows, see [Data Intelligence](https://dev.virlo.ai/docs/intelligence)) and its own `intelligence_status`: `ready`, `pending`, `failed`, `skipped` (no analysis for this video), or `disabled` (not turned on). A slideshow also has `slides`, a list of `{ image_url, position }`, where `image_url` is a storage file name, not a link. Its slide text is in `intelligence.panel_texts`.
- `analysis` (object | null, optional): Only with `data_intelligence`: the "what's working" summary, or `null` until it's ready. It has `key_insight`, `what_works` (each with its `evidence_video_ids`), `winning_hooks`, `best_formats`, `top_subjects`, `relevance` (only if you sent `context`), and `degraded`. `degraded` is `true` when under 60% of the videos had their AI breakdown, so the summary leans on plain numbers.
- `intelligence_status` (string | null, optional): Progress of the whole Data Intelligence job: `pending`, `ready`, `failed`, or `skipped`. `null` if it wasn't turned on for this run.
- `error` (string | null, optional): Why the run failed, if it did.
- `credits_refunded` (integer, optional): Credits paid back for this run: the whole charge if it failed, or an add-on fee that produced nothing. Only there when a refund was made. It stays on the run after the 24-hour status check expires.

### List your runs

`GET /v1/satellite/runs` lists your team's runs, newest first. Each paid lookup is its own row, so a creator you looked up twice appears twice. Free.

- `type` (string, optional): `creator_lookup`, `sound_lookup`, `hashtag_lookup`, or `video_outlier`. Creators from a batch show up as `creator_lookup`.
- `platform` (string, optional): `tiktok`, `youtube`, or `instagram`.
- `limit` (integer, optional): Runs per page, from 1. Default `25`. Anything above 100 counts as 100.
- `offset` (integer, optional): How many runs to skip. Default `0`.

Each row has `run_id`, `type`, `platform`, `status`, `subject`, `created_at`, `completed_at`, and `credits_used`. The response also has `total`, `limit`, and `offset`.

### Page through a run's videos

`GET /v1/satellite/runs/:run_id/videos` returns a run's videos a page at a time. Free.

- `limit` (integer, optional): From 1 to 200, default `25`. Above 200 returns `400`.
- `offset` (integer, optional): How many videos to skip. Default `0`.
- `sort` (string, optional): `recent` or `popular` (views, likes, comments, shares, and saves combined). Leave it out to keep the saved order.
- `content_type` (string, optional): `all` (default), `video`, or `slideshow` (photo carousels). On TikTok, `slideshow` only finds carousels on `data_intelligence` lookups, and `video` currently returns carousels too.

The response has `videos`, `total` (after the `content_type` filter), `limit`, `offset`, `sort`, and `content_type`.

**One run request:**

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

**List request:**

```bash
curl -G https://api.virlo.ai/v1/satellite/runs \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d type=creator_lookup \
  -d limit=25
```

**Videos request:**

```bash
curl -G https://api.virlo.ai/v1/satellite/runs/a1b2c3d4-e5f6-7890-abcd-ef1234567890/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d sort=popular \
  -d limit=50
```

**Response One run:**

```json
{
  "data": {
    "run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "type": "creator_lookup",
    "platform": "tiktok",
    "status": "completed",
    "subject": "khaby.lame",
    "created_at": "2026-09-24T17:23:37.665014+00:00",
    "completed_at": "2026-09-24T17:24:05.374+00:00",
    "credits_used": null,
    "request": {
      "context": "which hooks drive their most-viewed videos",
      "max_videos": 30,
      "data_intelligence": true,
      "trend_analysis": false
    },
    "result": {
      "username": "khaby.lame",
      "platform": "tiktok",
      "videos": [
        {
          "id": "7234567890123456789",
          "views": 85000000,
          "intelligence_status": "ready",
          "intelligence": {
            "hook_type": "relatable_scenario",
            "content_format": "reaction",
            "is_sponsored": false,
            "brand_safety_tier": "safe"
          }
        }
      ],
      "run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "credits_charged": 50
    },
    "analysis": {
      "key_insight": "Silent reactions to overcomplicated life hacks drive his biggest videos.",
      "what_works": [
        {
          "insight": "Reaction videos that undo a needlessly complex hack beat his median by 6x.",
          "evidence_video_ids": ["7229876543210987654"]
        }
      ],
      "winning_hooks": ["Relatable scenario: someone overcomplicating a simple task"],
      "best_formats": "Short silent reactions under 25 seconds.",
      "top_subjects": ["Life hacks", "Everyday tasks"],
      "relevance": "Lead with the overcomplicated version, then the simple fix.",
      "degraded": false
    },
    "intelligence_status": "ready",
    "error": null
  }
}
```

**Response List:**

```json
{
  "data": {
    "runs": [
      {
        "run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "type": "creator_lookup",
        "platform": "tiktok",
        "status": "completed",
        "subject": "khaby.lame",
        "created_at": "2026-09-24T17:23:37.665014+00:00",
        "completed_at": "2026-09-24T17:24:05.374+00:00",
        "credits_used": null
      }
    ],
    "total": 1,
    "limit": 25,
    "offset": 0
  }
}
```

**Response Videos:**

```json
{
  "data": {
    "videos": [
      {
        "id": "7229876543210987654",
        "title": "Why make it hard?",
        "views": 210000000,
        "publish_date": "2025-02-10T08:00:00.000Z",
        "url": "https://www.tiktok.com/@khaby.lame/video/7229876543210987654",
        "intelligence_status": "ready"
      }
    ],
    "total": 30,
    "limit": 50,
    "offset": 0,
    "sort": "popular",
    "content_type": "all"
  }
}
```

---

## Batch creator lookup

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

Starts lookups for up to 25 creators in one request. You get a batch ID plus a job ID for each creator, and each creator gets the same result as a single lookup. The differences:

- **No trend analysis or Data Intelligence.** Sending `trend_analysis`, `data_intelligence`, `context`, or `slideshow_sort` returns `400`.
- **Free 6-hour repeats.** A creator your team looked up in the last 6 hours, with at least these options, comes back `completed` at once, free, with the saved result (same `job_id`).
- **No 5-per-minute limit.** Batches don't count toward the limit on single lookups.

Cost:
$0.50 per creator that starts a lookup (10 creators = $5.00), plus $0.50 per creator that needs a new audience snapshot.

Your balance must cover the whole batch, or it's rejected with `402` and nothing runs. A creator whose lookup fails, such as a handle that doesn't exist, is refunded like a single lookup. If Virlo can't start one creator's lookup (a problem on Virlo's side), it shows `"status": "failed"` with an empty `job_id`, and you aren't charged for it, audience fee included.

### Body parameters

- `creators` (array, required): 1 to 25 entries like `{ "platform": "tiktok", "username": "khaby.lame" }`.

Optional: `include`, `max_videos`, `outlier_threshold`, `cross_links`, `audience_demographics`, `audience_geography`, and `freshness_days`. They work as on a [single lookup](https://dev.virlo.ai/docs/satellite#start-creator-lookup) and apply to every creator.

**cURL request:**

```bash
curl -X POST https://api.virlo.ai/v1/satellite/creators/batch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "creators": [
      { "platform": "tiktok", "username": "khaby.lame" },
      { "platform": "youtube", "username": "mkbhd" },
      { "platform": "instagram", "username": "therock" }
    ],
    "include": "videos,outliers",
    "cross_links": true,
    "max_videos": 30
  }'
```

**Response 202 One cached:**

```json
{
  "data": {
    "batch_id": "b7e1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
    "status": "partial",
    "total": 3,
    "completed": 1,
    "failed": 0,
    "processing": 2,
    "creators": [
      { "platform": "tiktok", "username": "khaby.lame", "job_id": "3f2a9c1e-6b7d-4e8f-9a0b-1c2d3e4f5a6b", "status": "processing" },
      { "platform": "youtube", "username": "mkbhd", "job_id": "8c7d6e5f-4a3b-4c2d-9e1f-0a9b8c7d6e5f", "status": "completed" },
      { "platform": "instagram", "username": "therock", "job_id": "d4c3b2a1-9f8e-4d7c-8b6a-5f4e3d2c1b0a", "status": "processing" }
    ]
  }
}
```

**Response 400:**

```json
{
  "message": ["property trend_analysis should not exist"],
  "error": "Bad Request",
  "statusCode": 400,
  "code": "validation_error"
}
```

---

## Check batch status

**Endpoint:** `GET https://api.virlo.ai/v1/satellite/creators/batch/:batch_id`

Shows progress only: one status per creator, plus totals. For each creator's data, check its `job_id` with [Check lookup status](https://dev.virlo.ai/docs/satellite#poll-lookup-status). Free, for 24 hours.

- `processing`: nothing has finished yet.
- `partial`: some creators are done (or failed) and some are still running.
- `completed`: nothing is still running. **Some creators may have failed**, so check the `failed` count.
- `failed`: every creator failed.

A creator can also show `expired` once its 24-hour record is gone. It counts as failed, but if it finished, its result is still in its saved run (its `job_id` is the [run ID](https://dev.virlo.ai/docs/satellite#durable-runs)). The batch itself isn't saved, and it doesn't show refunds: check each creator's job for `credits_refunded`.

### Path parameters

- `batch_id` (string, required): The batch ID from the batch call.

**cURL request:**

```bash
curl https://api.virlo.ai/v1/satellite/creators/batch/b7e1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200 One failed:**

```json
{
  "data": {
    "batch_id": "b7e1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
    "status": "completed",
    "total": 3,
    "completed": 2,
    "failed": 1,
    "processing": 0,
    "creators": [
      { "platform": "tiktok", "username": "khaby.lame", "job_id": "3f2a9c1e-6b7d-4e8f-9a0b-1c2d3e4f5a6b", "status": "completed" },
      { "platform": "youtube", "username": "mkbhd", "job_id": "8c7d6e5f-4a3b-4c2d-9e1f-0a9b8c7d6e5f", "status": "completed" },
      { "platform": "instagram", "username": "therock", "job_id": "d4c3b2a1-9f8e-4d7c-8b6a-5f4e3d2c1b0a", "status": "failed" }
    ]
  }
}
```

**Response 404:**

```json
{
  "message": "Batch not found or expired",
  "error": "Not Found",
  "statusCode": 404,
  "code": "not_found"
}
```

---

## Errors

- `400 Bad Request`: Something in the request is wrong. Not billed.
  - **Lookup:** an unknown platform (it must be lowercase), a handle with `/`, `?`, `#`, `%`, or spaces, `max_videos` outside 1 to 100, `outlier_threshold` below 0.1, `freshness_days` outside 0 to 365, `context` over 600 characters, or an unknown parameter.
  - **Batch:** no creators, more than 25, or an option batches don't support.
  - **Run endpoints:** a run ID that isn't a valid ID, a `limit` out of range, or an unknown `type`, `sort`, or `content_type`.
- `401 Unauthorized`: The API key is missing or invalid. Send it as `Authorization: Bearer YOUR_API_KEY`.
- `402 Payment Required`: Your balance is too low. The message says how much is needed. Add funds at [dev.virlo.ai/dashboard/billing](https://dev.virlo.ai/dashboard/billing). See [Billing and pricing](https://dev.virlo.ai/docs/credits).
- `404 Not Found`: The job ID is unknown or older than 24 hours (read the [saved run](https://dev.virlo.ai/docs/satellite#durable-runs) instead), the batch or run ID doesn't exist, or the username is missing from the path.
- `429 Too Many Requests`: Too many lookups started too fast. Single lookups allow 5 per minute, 100 per hour, and 1,000 per day, and rejected `400` requests count too. Status checks, batches, and run reads use the general daily limit. Wait `retry_after` seconds. Ignore the old duplicates (`resetAt`, `resetAtFormatted`, `retryAfter`) and use the underscore names. See [Rate limits](https://dev.virlo.ai/docs/rate-limits).

**A creator that can't be found isn't an error response.** The start call succeeds and is billed $0.50. The status check then returns `failed` with `No posts found for @username on tiktok...` (on YouTube, no Shorts found), and the charge is refunded. Fix the handle and start again. With an audience add-on, the lookup doesn't fail: it completes with empty stats and just the audience data.

---

## Other lookups

The same start, check, and re-read flow works for:

- **[Sound lookup](https://dev.virlo.ai/docs/satellite/sounds):** the TikTok videos or Instagram reels using a sound, with top creators, hashtags, and how fast it's spreading. $0.50, or $1.00 with trend analysis. No YouTube.
- **[Hashtag lookup](https://dev.virlo.ai/docs/satellite/hashtags):** videos posted under a hashtag on TikTok, Instagram, or YouTube Shorts, with top creators, related hashtags, and top sounds. $0.50, up to $2.50 with `depth=full` and trend analysis.
- **[Video outlier check](https://dev.virlo.ai/docs/satellite/video-outlier):** whether one video beat its creator's usual views. $0.50 per check.

---

More in Deep-dive lookups:

- [Creator lookup add-ons](https://dev.virlo.ai/docs/satellite/creators.md)
- [Video outlier check](https://dev.virlo.ai/docs/satellite/video-outlier.md)
- [Sound lookup](https://dev.virlo.ai/docs/satellite/sounds.md)
- [Hashtag lookup](https://dev.virlo.ai/docs/satellite/hashtags.md)
