Deep-dive lookup

Video outlier check

Find out whether a video is a real outlier or only looks big because the creator is big, and see what set it apart from their usual posts.

At a glance
What it does
Scores one TikTok, YouTube or Instagram video against the creator's own recent videos. Use it to vet a video before you copy it, pay to boost it, or build a brief around it.
You send
The video's link and its platform.
You get back
A job ID right away, then a verdict such as viral, how many times over it beat their usual, where it ranks among their videos, and what set it apart.
Cost
$0.50 per check. The same video again within 6 hours is free, and a check that fails is refunded. Status checks and saved results are free.
How long
Usually 15 to 60 seconds, a few minutes at busy times.

It's one of Virlo's deep-dive lookups, under /v1/satellite/.

Prefer a JSON body? POST /v1/satellite/video takes the same options and costs the same. See Start with a JSON body.


POST/v1/satellite/video-outlier

Start a video outlier check

Returns a job_id in about a second. Virlo then compares the video with up to 100 of the creator's recent videos.

Cost per request:$0.50

The same video again within 6 hours returns the saved result free, marked cached: true. That counts per team. A check that fails is refunded in full.

Body parameters

Send only these two fields. Anything missing or extra returns a 400.

  • Name
    url
    Type
    string
    Required
    *
    Description

    The full link, including https://. It must match platform: Virlo won't catch a mismatch, and still charges.

  • Name
    platform
    Type
    string
    Required
    *
    Description

    tiktok, youtube or instagram, lowercase.

Limits: 5 starts a minute, 100 an hour, 1,000 a day. Status checks don't count.

Details for developers
  • Limits reset on the clock: each minute, each hour and at midnight UTC.
  • Requests rejected with a 400 still count.
  • X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset describe the window closest to running out. A 429 adds Retry-After (seconds). See Rate limits.

Request

POST
/v1/satellite/video-outlier
curl -X POST https://api.virlo.ai/v1/satellite/video-outlier \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://www.instagram.com/reels/DVMBt9pE4ob/",
    "platform": "instagram"
  }'

Response

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

GET/v1/satellite/video-outlier/status/:job_id

Check the status

Free. Returns processing while the check runs, then completed with the result or failed with a reason in error. After 24 hours the job ID expires (404); the saved run keeps the result.

Path parameters

  • Name
    job_id
    Type
    string
    Required
    *
    Description

    The job_id from the start call.

See What you get back for how to read a finished check.

Request

GET
/v1/satellite/video-outlier/status/:job_id
curl https://api.virlo.ai/v1/satellite/video-outlier/status/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer YOUR_API_KEY"

Response

{
  "data": {
    "status": "completed",
    "result": {
      "video": {
        "url": "https://www.youtube.com/watch?v=jtXu_HWYUko",
        "title": "Jasmin is too fast! 🧠😏",
        "views": 15358839,
        "likes": 162682,
        "comments": 550,
        "publish_date": "2026-10-01T18:30:12.000Z",
        "publishDate": "2026-10-01T18:30:12.000Z"
      },
      "creator": {
        "username": "JasminandJames",
        "platform": "youtube",
        "profile": {
          "followers": 43400000,
          "following": 0,
          "avatar_url": "https://yt3.googleusercontent.com/1jSHV5QrtGDbXIGAbChuH0rM_7GxpPnhU5uT9JmzkNGN2fX2X2Ywb30bXTxMSjHG7AYZZ16OaA=s160-c-k-c0x00ffffff-no-rj",
          "url": "https://www.youtube.com/@JasminandJames",
          "description": "Hey and welcome to our official YouTube channel ♡\n\nHere to make you smile! 🥹🫶🏻\n\nWe truly appreciate every one of you for showing us constant love and support, thank you for subscribing! 🩷 \nWELCOME TO THE FAMILY! #JJFAM \n\nROAD TO 100M SUBSCRIBERS 🥰\n",
          "is_verified": null,
          "total_videos": 27433,
          "total_likes": null,
          "total_views": 81416914621,
          "total_posts": null,
          "category": null,
          "attribution": " linktr.ee/jasminandjames",
          "handle": "JasminandJames"
        }
      },
      "analysis": {
        "videos_analyzed": 20,
        "median_views": 239514,
        "avg_views": 484989,
        "outlier_score": 64.13,
        "weighted_score": 51.54,
        "percentile": 100,
        "std_deviation": 505740,
        "z_score": 29.41,
        "performance_label": "mega_viral"
      },
      "comparison": {
        "rank": {
          "position": 1,
          "of": 21
        },
        "engagement_rate": {
          "video": 0.0106,
          "typical": 0.0101,
          "lift": 1.05
        },
        "duration_seconds": null,
        "rare_hashtags": [],
        "posted_days_ago": 40,
        "top_videos": [
          {
            "url": "https://www.youtube.com/watch?v=jtXu_HWYUko",
            "title": "Jasmin is too fast! 🧠😏",
            "views": 15358839,
            "publish_date": "2026-10-01T18:30:12.000Z",
            "is_this_video": true
          },
          {
            "url": "https://www.youtube.com/shorts/kPSoEwZCq2k",
            "title": "BEHIND THE SCENES! 🎥🐶🤣 #puppy #dance #viral",
            "views": 1682941,
            "publish_date": "2026-11-10T12:45:19.000Z",
            "is_this_video": false
          },
          {
            "url": "https://www.youtube.com/shorts/9F8UvGgzdDU",
            "title": "SUBSCRIBE FOR 7 YEARS OF GOOD LUCK! 🍀😅 - #dance #funny #couple #shorts",
            "views": 1403049,
            "publish_date": "2026-11-10T11:30:28.000Z",
            "is_this_video": false
          }
        ],
        "key_findings": [
          {
            "kind": "rank",
            "text": "Their most viewed of the 21 recent videos checked."
          }
        ]
      }
    },
    "run_id": "c1ad6cc6-b7fa-443b-bc8b-02e53ac63104",
    "finalized": true
  }
}

What you get back

The result (data.result) has four parts: video, creator, analysis (the verdict) and comparison (what set the video apart).

The verdict

Start with performance_label and outlier_score. outlier_score says how many times over the video beat what this creator usually gets, measured against up to 100 of their recent videos. 6.56 means about 6.6 times their usual. It's based on their typical video, so one old mega-hit doesn't move the bar.

performance_labelAgainst their usual views
mega_viral10 times or more
viral3 to 10 times
above_average1.5 to 3 times
averageLess than 1.5 times, including flops

percentile is a good second check: 89 means it beat 89% of the creator's recent videos. New videos score low at first, so judge them after a few days.

What set it apart

comparison shows how this video differs from the creator's other recent videos. These are differences, not proven causes. It's null with fewer than 5 other videos to compare, and absent on checks made before November 2026.

  • Name
    key_findings
    Type
    array
    Description

    Up to 5 plain sentences you can quote, each with kind (rank, engagement, length, hashtags, too_new) and text. A finding appears only when the numbers clear the bar, so the list can be empty.

  • Name
    rank
    Type
    object
    Description

    position among the of recent videos checked. 1 is their most viewed.

  • Name
    engagement_rate
    Type
    object | null
    Description

    Likes and comments per view: video, their typical, and lift (1.5 is 50% above). Don't compare it across platforms.

  • Name
    duration_seconds
    Type
    object | null
    Description

    video and their typical length. null when the platform doesn't report length.

  • Name
    rare_hashtags
    Type
    string[]
    Description

    Hashtags on this video that the creator rarely uses, leaving out catch-alls like fyp.

  • Name
    posted_days_ago
    Type
    integer | null
    Description

    How old the video is. Under 3 days, its views may still be climbing.

  • Name
    top_videos
    Type
    array
    Description

    Their three most viewed recent videos: url, title, views, publish_date and is_this_video.

Full field reference

data: result and run_id (equal to your job_id) only when completed; error only when failed.

result.video: url, title, views, likes, comments and publish_date (UTC, or null). Ignore publishDate, an old duplicate due for removal.

result.creator: username, platform and profile (null if it couldn't load). profile holds followers, following, avatar_url, url, description, is_verified, total_videos, total_likes, total_views, total_posts and category, plus:

  • bio_link on TikTok and Instagram, or attribution on YouTube (the channel header's link text, such as X, not a URL).
  • country, a two-letter code, on TikTok only.
  • On YouTube, is_verified is always null and following always 0.
  • Numbers a platform doesn't share come back null or 0. Instagram often shows 0 for total_videos and total_posts.

result.analysis:

  • videos_analyzed: how many recent videos the check compared against. Up to 100, and often 20 to 30 on TikTok and YouTube, where the platform returns fewer. At 0 there's no baseline: ignore the scores and label.
  • median_views, avg_views: the creator's typical and average views.
  • weighted_score: for ranking videos across creators. The same jump scores higher for a creator with bigger usual views, and it's 0 if the video didn't beat their usual. Not the same as the Virality Score elsewhere, which is measured against followers.
  • percentile: 0 to 100, can have decimals.
  • std_deviation: how much the creator's views swing.
  • z_score: how unusual these views are for this creator. It stays low for creators with a few huge hits, even when outlier_score is high.

Wait for the result

Check the status every 10 to 15 seconds, as below. Still processing after 10 minutes (rare, such as after a server restart)? Start a new check.

Or have Virlo notify your server when a check finishes or fails: use the satellite.lookup.completed webhook (data.type is video_outlier).

Start and wait

const API = 'https://api.virlo.ai/v1';
const headers = {
  'Authorization': 'Bearer YOUR_API_KEY',
  'Content-Type': 'application/json'
};

async function analyzeVideo(url, platform) {
  // 1. Start the check ($0.50)
  const start = await fetch(`${API}/satellite/video-outlier`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ url, platform })
  });
  if (!start.ok) throw new Error(`Start failed: ${start.status}`);
  const jobId = (await start.json()).data.job_id;

  // 2. Check every 10 seconds until it finishes. Give up after 10 minutes.
  const deadline = Date.now() + 10 * 60 * 1000;
  while (Date.now() < deadline) {
    await new Promise((resolve) => setTimeout(resolve, 10_000));

    const res = await fetch(`${API}/satellite/video-outlier/status/${jobId}`, { headers });
    if (!res.ok) throw new Error(`Status check failed: ${res.status}`);
    const job = (await res.json()).data;

    if (job.status === 'completed') return job.result;
    if (job.status === 'failed') throw new Error(job.error ?? 'Check failed');
  }
  throw new Error(`Still processing after 10 minutes (job ${jobId})`);
}

Re-read past runs

Every check, failed ones too, is saved as a run that is free to read and never expires. Its ID is your job_id.

  • GET /v1/satellite/runs?type=video_outlier: your checks, newest first.
  • GET /v1/satellite/runs/:run_id: one check, with the verdict at data.result.analysis. Its top-level analysis is always null here.

To see what changed since an earlier check of the same video, use GET /v1/satellite/runs/:run_id/compare (free). To check it again, use POST /v1/satellite/runs/:run_id/refresh ($0.50, free within 6 hours).

The creator's comparison videos aren't saved, so /runs/:run_id/videos returns an empty list. See all run fields.


Errors

Error responses aren't wrapped in data and are never charged. Check the top-level code, not the message. See Errors.

A failed check is charged when it starts and refunded in full, shown as credits_refunded. For example, the video is deleted or private, or its creator can't be found. Open the link in a logged-out browser first.

  • Name
    400 Bad Request
    Description

    Bad url or platform, or an extra field. message is usually a list, sometimes a string. If it isn't about your input (like Failed to analyze video), the problem is on Virlo's side: retry shortly.

  • Name
    401 Unauthorized
    Description

    No API key (missing_api_key), or a wrong or inactive one (invalid_api_key).

  • Name
    402 Payment Required
    Description

    Balance under $0.50 (50 credits, at $0.01 each). Add funds.

  • Name
    404 Not Found
    Description

    The job ID is unknown, another account's, or over 24 hours old. Read the saved run.

  • Name
    429 Too Many Requests
    Description

    Wait retry_after seconds, then retry.

Was this page helpful?