Deep-dive lookup

Video outlier check

Find out whether a video is a real outlier, meaning it beat its creator's usual views, or only looks big because the creator is big.

At a glance
What it does
Tells you if one TikTok, YouTube or Instagram video is a real breakout for its creator.
You send
The video's link and its platform.
You get back
A job ID right away, then the video's stats, the creator's profile, an outlier score and a verdict such as viral.
Cost
$0.50 per check, charged at the start, even if the check fails. 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/.


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

Each request is billed, even for a link you already checked. Creator, sound and hashtag lookups repeat free within 6 hours; this one doesn't.

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.

How to read the result

The result (data.result) has three parts: video, creator and analysis. In analysis, start with:

  • outlier_score: views divided by the creator's median views (half their recent videos got more, half fewer). 6.56 means about 6.6 times their usual.
  • performance_label: the verdict.
performance_labeloutlier_score
mega_viral10 or more
viral3 to under 10
above_average1.5 to under 3
averageUnder 1.5, 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; judge them after a few days.

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: up to 100, fewer if the creator has fewer videos or the platform returns fewer. At 0 there's no baseline: ignore the scores and label.
  • median_views, avg_views: the creator's median and mean views.
  • weighted_score: ln(outlier_score) × ln(median_views), 0 if the video didn't beat the median. It rewards beating a bigger baseline, so use it to rank videos. Not the same as the Virality Score weighted_score elsewhere, which uses followers.
  • percentile: 0 to 100, can have decimals.
  • std_deviation: how much the creator's views swing around the mean.
  • z_score: how unusual these views are, given that swing (0 is the mean, negative is below). It stays low for creators with a few huge hits, even when outlier_score is high.

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.instagram.com/reels/DVMBt9pE4ob/",
        "title": "Japan is turning footsteps into electricity! Using piezoelectric tiles, every step you take generates a small amount of energy...",
        "views": 247862,
        "likes": 27368,
        "comments": 185,
        "publish_date": "2026-02-25T16:40:48.000Z",
        "publishDate": "2026-02-25T16:40:48.000Z"
      },
      "creator": {
        "username": "notivion.ig",
        "platform": "instagram",
        "profile": {
          "followers": 178543,
          "following": 12,
          "avatar_url": "https://instagram.fsac1-2.fna.fbcdn.net/v/t51.82787-19/770751058_n.jpg",
          "url": "https://www.instagram.com/notivion.ig/",
          "description": "Small artist and animator",
          "is_verified": false,
          "total_videos": 0,
          "total_likes": null,
          "total_views": null,
          "total_posts": 0,
          "category": null,
          "bio_link": null
        }
      },
      "analysis": {
        "videos_analyzed": 100,
        "median_views": 37779,
        "avg_views": 131447,
        "outlier_score": 6.56,
        "weighted_score": 19.83,
        "percentile": 89,
        "std_deviation": 319747,
        "z_score": 0.36,
        "performance_label": "viral"
      }
    },
    "run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}

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, at another $0.50.

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.

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 different: it started, so it's charged. 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?