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.
- 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.
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.
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 matchplatform: Virlo won't catch a mismatch, and still charges.
- Name
platform- Type
- string
- Required
- *
- Description
tiktok,youtubeorinstagram, 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
400still count. X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Resetdescribe the window closest to running out. A429addsRetry-After(seconds). See Rate limits.
Request
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"
}
}
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_idfrom the start call.
See What you get back for how to read a finished check.
Request
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_label | Against their usual views |
|---|---|
mega_viral | 10 times or more |
viral | 3 to 10 times |
above_average | 1.5 to 3 times |
average | Less 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) andtext. A finding appears only when the numbers clear the bar, so the list can be empty.
- Name
rank- Type
- object
- Description
positionamong theofrecent videos checked.1is their most viewed.
- Name
engagement_rate- Type
- object | null
- Description
Likes and comments per view:
video, theirtypical, andlift(1.5is 50% above). Don't compare it across platforms.
- Name
duration_seconds- Type
- object | null
- Description
videoand theirtypicallength.nullwhen 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_dateandis_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_linkon TikTok and Instagram, orattributionon YouTube (the channel header's link text, such asX, not a URL).country, a two-letter code, on TikTok only.- On YouTube,
is_verifiedis alwaysnullandfollowingalways0. - Numbers a platform doesn't share come back
nullor0. Instagram often shows0fortotal_videosandtotal_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. At0there'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's0if 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 whenoutlier_scoreis 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 atdata.result.analysis. Its top-levelanalysisis alwaysnullhere.
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
urlorplatform, or an extra field.messageis usually a list, sometimes a string. If it isn't about your input (likeFailed 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_afterseconds, then retry.

