Content Research Agents
Want to know what is working in a niche on TikTok, YouTube, and Instagram? A Content Research Agent collects the videos and reports the trends, standout creators, sounds, and hashtags, once or on a schedule.
- What it does
- Searches the platforms for your topic, keeps the videos that fit, and reports what is working and why.
- You send
- Your API key (Quickstart), a one-sentence
intent, some keywords, and whether it runs once or on a schedule. - You get back
- An agent
idright away. Once the run is done, you read its videos, creators, trends, and report, all free except hooks. - Cost
- $0.50 per run, $1.50 with Data Intelligence (AI video breakdowns). One-time agents pay at creation, recurring ones after each run.
- How long
- Half of runs finish collecting in under 8 minutes, and 9 in 10 in under 20. The AI report follows within a few minutes.
How an agent works
- Get keywords (free). Send your
intent, one sentence about what you want, toPOST /v1/agents/suggest-keywords. - Create the agent. Send that
intent, thekeywords, and anyexclude_keywordsyou agree with toPOST /v1/agents. You get itsidat once. - Wait until
GET /v1/agents/:idshowsfinalized: true. Check every 15 seconds, or whateverretry_after_secondssays. This polling is free. Or use thecontent_research_agent.run.completedwebhook. - Read the results. Start with the summary, then videos, creator outliers (ready about a minute after
finalized), and trends.
Virality Score (weighted_score, on creator and hook rows) shows how far views outran follower count, with extra credit for bigger accounts (formula). 35 and up is exceptional, 25 to 35 very strong, 18 to 25 strong, 10 to 18 promising.
Which call answers my question?
| Your question | Call |
|---|---|
| What's working? | Get summary |
| Which videos did best? | Get videos with order_by=views |
| Why is it working? | Get latest trends, Get latest analysis |
| Which creators should we work with? | Get creator outliers, then similar creators and benchmarks |
| Which sounds, hashtags, and hooks? | Get sounds, Get hashtags, Get hooks |
| Why so few videos? | List runs |
| What did autopilot change? | Get activity |
Writing a good intent
Keywords decide where the agent looks. The intent decides which videos it keeps. Write one concrete sentence, about 40 to 250 characters (500 at most):
[Find/Monitor] [content type] about [niche] for [use case], [not / exclude X].
Good: "Find beginner skincare routines that name drugstore products, not dermatologist lectures." Weak: bare topics ("skincare, beauty") or vague wishes ("I want to find viral video"). More real examples: Intent cookbook.
Suggest keywords
Turns your intent into 7 to 12 graded keywords plus words to exclude, ready for Create agent. Free, and it creates nothing.
- Name
intent- Type
- string
- Required
- *
- Description
One sentence, up to 500 characters. Reuse it for Create agent.
- Name
topic_hint- Type
- string
- Description
A short topic name to steer the result.
- Name
platforms- Type
- string[]
- Description
youtube,tiktok,instagram. Notmeta_ads.
- Name
mode- Type
- string
- Description
create(default) builds a fresh list,refreshreplaces keywords that stopped finding much,opportunitysuggests new angles. Apply the result with Update agent.
- Name
existing_keywords- Type
- string[]
- Description
Current keywords, for
refreshoropportunity.
- Name
desired_count- Type
- integer
- Description
Accepts 1 to 50, but you always get 7 to 12.
- Name
use_web_grounding- Type
- boolean
- Description
Check the web for current phrasing, for news-driven topics.
quality.passes: true means a quality.score of 65 or more and no critical issue. It grades the keyword list, not your intent: a vague intent like "coffee" can still score 100.
Full field reference
quality.issues items have code, severity (critical, warning, or info), message, and often offenders. Codes: too_few_keywords, too_many_keywords, single_word_keywords, overly_long_keywords, duplicate_keywords, low_cluster_cohesion, empty_set. A low quality.stats.core_token_coverage means a scattered list and more off-topic videos.
Request
curl -X POST https://api.virlo.ai/v1/agents/suggest-keywords \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"intent": "Track viral protein-recipe content for a fitness brand",
"topic_hint": "Protein Recipes",
"platforms": ["tiktok", "instagram"],
"desired_count": 7
}'
Response
{
"data": {
"keywords": [
"protein recipes",
"high protein meals",
"healthy protein recipes",
"easy protein recipes",
"protein snack ideas",
"fitness protein recipes",
"muscle building recipes"
],
"exclude_keywords": ["powder", "shake", "supplement", "bar", "diet", "vegan", "vegetarian"],
"reasoning": "This set focuses on 'protein recipes' as the core, with variations covering different meal types and fitness goals.",
"quality": {
"score": 100,
"passes": true,
"issues": [],
"stats": {
"count": 7,
"avg_words_per_keyword": 2.86,
"single_word_count": 0,
"long_keyword_count": 0,
"duplicate_count": 0,
"core_token_coverage": 0.86,
"core_token": "protein"
}
},
"timely_context_used": false
}
}
Create agent
Creates an agent and starts its first run right away.
Cost: $0.50 per run, or $1.50 with Data Intelligence.
- One-time (the first example): charged at creation, so
X-Costshows0.50(or1.50). No refund if the run fails. - Recurring: free to create (the
X-Costresponse header says0.00). Each run, the first starting right away, is charged when it finishes. Charges show on your Usage page, not in a header. Failed runs are free;partial_failureruns (one keyword or platform failed) cost full price. Runs repeat until you pause (Update agent,active: false) or delete the agent. - Your balance must cover the first run, or you get
402(insufficient_credits).
- Name
is_recurring- Type
- boolean
- Required
- *
- Description
falseruns once,truerepeats oncadence. Can't be changed later. The string"false"is rejected.
- Name
intent- Type
- string
- Required
- *
- Description
Up to 500 characters. See Writing a good intent.
- Name
keywords- Type
- string[]
- Required
- *
- Description
1 to 50 search phrases; 7 to 12 multi-word phrases work best. A leading
#is dropped (#latteartsearcheslatteart), so writelatte artfor the phrase. The agent may search refined versions, shown inintent_keywords.
- Name
cadence- Type
- string
- Description
Required when recurring:
"daily","weekly","monthly", or a cron expression that runs at most once a day. On a one-time agent it is ignored and comes backnull.
- Name
platforms- Type
- string[]
- Description
youtube,tiktok,instagram. Defaults to all three.
Filter by views or date when you read the videos, not here: min_views, time_range, or any unlisted field returns a free 400 error.
Details for developers
Schedules run in UTC. daily is 0 0 * * *, weekly is 0 0 * * 0 (Sundays), and monthly is 0 0 1 * *, the cron form the response shows. With these shortcuts, each agent gets a fixed start time within the 6 hours after midnight UTC. A custom cron runs at the time you set. A run starts within about 30 minutes of next_run_at.
Errors come back before anything is charged. Each 400 has code: "validation_error". Its message is usually a list, but some checks, such as intent length, return a string.
Request
# $0.50 charged now (X-Cost: 0.50). Runs once.
# Shortened to 3 keywords. Pass the 7 to 12 from suggest-keywords.
curl -X POST https://api.virlo.ai/v1/agents \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"is_recurring": false,
"intent": "Track viral protein-recipe content for a fitness brand",
"keywords": ["high protein recipe", "protein meal prep", "protein snack ideas"],
"platforms": ["youtube", "tiktok", "instagram"],
"name": "Protein Recipes"
}'
Response
{
"data": {
"id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"name": "Protein Recipes",
"is_recurring": false,
"intent_keywords": null,
"cadence": null,
"next_run_at": null,
"last_run_at": null,
"job_id": "c29bfbf3-4fa6-470b-80d3-53364f916ea8",
"latest_run": {
"id": "5c8b1f20-3a4d-4e7f-9b12-6d2a1c7e8f90",
"status": "pending"
}
},
"message": "Agent created"
}
Save data.id: paste it over {agent_id} in every later call. Ignore job_id.
Get agent
Settings, latest run, and live progress: the call you repeat while you wait. Free.
- Name
finalized- Type
- boolean
- Description
trueonce collecting and the AI report are done. Wait for this, notlatest_run.status.
- Name
pending_jobs- Type
- object[]
- Description
AI work still running. Wait each job's
retry_after_seconds(currently 15) before checking again.
- Name
latest_run- Type
- object
- Description
Shaped like Get run.
statusgoespending,processing, thencompleted,partial_failure(one platform or keyword failed, results still usable), orfailed.
- Name
intent_keywords- Type
- string[]
- Description
The phrases actually searched, built from
intentandkeywords. Set soon after a run starts; editing either clears it until the next run.
- Name
last_run_at- Type
- string
- Description
When the last run fully wrapped up, creator outliers included.
nullon a new agent until then.
- Name
is_processing- Type
- boolean
- Description
Always
falseon one-time agents, even mid-run. Not a done signal.
- Name
autopilot- Type
- boolean
- Description
truewhen autopilot is on, the default.autonomy_levelandautopilot_unlockedare older fields; read this one instead.
- Name
pinned_keywords- Type
- string[]
- Description
The keywords you set. Autopilot keeps all of them and only adds around them, so
keywordscan hold more.nullfor agents made in the Virlo app.
For a progress bar, use stage, progress_pct, and eta_seconds (values).
When can I read my results?
| You see | Safe to read |
|---|---|
latest_run.status: "completed" | Nothing yet. Only collecting is done. |
finalized: true | Summary, videos, slideshows, ads, sounds, hashtags, analysis, trends |
last_run_at later than latest_run.started_at (on a new agent: not null) | Creator outliers |
A video's intelligence_status: "ready" | That video's Data Intelligence |
finalized does not wait for Data Intelligence. Videos Virlo will not analyze show intelligence_status: "skipped", often more than half of an agent's videos. A video still pending a day later will probably never be analyzed.
Request
curl https://api.virlo.ai/v1/agents/{agent_id} \
-H "Authorization: Bearer YOUR_API_KEY"
Response
{
"data": {
"id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"name": "Protein Recipes",
"is_recurring": true,
"active": true,
"intent": "Track viral protein-recipe content for a fitness brand",
"intent_keywords": ["high protein recipe ideas", "protein meal prep for the week", "easy protein snacks"],
"cadence": "0 0 * * 0",
"next_run_at": "2026-09-27T02:14:08.000Z",
"last_run_at": "2026-09-24T17:31:12.491Z",
"is_processing": false,
"autopilot": true,
"autonomy_level": "autopilot",
"autopilot_unlocked": true,
"cognition_enabled": true,
"pinned_keywords": ["high protein recipe", "protein meal prep", "protein snack ideas"],
"analysis": "No-cook overnight protein recipes are driving the most outsized reach this week...",
"analysis_data": { "key_highlight": "No-cook overnight protein recipes are driving the most outsized reach this week...", "themes": [] },
"analysis_batch_start": "2026-09-24T17:23:43.301+00:00",
"analysis_batch_end": "2026-09-24T17:29:14.320+00:00",
"pending_jobs": [],
"finalized": true,
"progress_pct": 100,
"stage": "completed",
"eta_seconds": 0,
"latest_run": {
"id": "5c8b1f20-3a4d-4e7f-9b12-6d2a1c7e8f90",
"status": "completed",
"videos_linked": 283,
"outliers_identified": 12,
"started_at": "2026-09-24T17:23:43.423Z",
"completed_at": "2026-09-24T17:31:11.085Z"
}
}
}
Get summary
A one-call digest of the latest run: status, counts, the top 5 creators and trends, and the main takeaway. The best first read once finalized is true. Free.
countsare the run's own tallies. For how many videos you can read, usetotalfrom Get videos.counts.soundsis alwaysnullfor now.counts.creatorscounts outlier creators only, and can read0until they're ready.run.platform_countsare counted before filters, so they can add up to more thanvideos_linked.- Some YouTube creators show
username: "channel". Get their link from creator outliers.
Request
curl https://api.virlo.ai/v1/agents/{agent_id}/summary \
-H "Authorization: Bearer YOUR_API_KEY"
Response
{
"data": {
"agent_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"agent_name": "Protein Recipes",
"is_recurring": true,
"finalized": true,
"progress_pct": 100,
"stage": "completed",
"eta_seconds": 0,
"run": {
"status": "completed",
"started_at": "2026-09-24T17:23:43.423Z",
"completed_at": "2026-09-24T17:31:11.085Z",
"total_videos": 283,
"videos_linked": 283,
"platform_counts": { "youtube": 104, "tiktok": 171, "instagram": 33 },
"outliers_identified": 12
},
"counts": { "videos": 283, "slideshows": 41, "sounds": null, "creators": 12 },
"top_creators": [
{ "username": "highproteinhannah", "platform": "tiktok", "followers": 84000, "weighted_score": 31.4 }
],
"top_trends": [
{ "name": "No-cook overnight protein", "stable_key": "no-cook-overnight-protein", "status": "new" }
],
"analysis_summary": "No-cook overnight protein recipes are driving the most outsized reach this week...",
"generated_at": "2026-09-24T17:40:04.276Z"
}
}
Get videos
The videos the agent collected. This is where you filter, by views, date, platform, or country, as often as you like, free, without re-running the agent. Newest first, 50 per page.
- Name
min_views- Type
- integer
- Description
Only videos with at least this many views.
- Name
platforms- Type
- string[]
- Description
youtube,tiktok,instagram, astiktok,youtubeor repeated. Plural:platformreturns400.
- Name
start_date / end_date- Type
- string
- Description
Publish date window, such as
2026-09-01or a full timestamp.
- Name
order_by / sort- Type
- string
- Description
publish_date(default),views, orcreated_at(when Virlo added it).desc(default) orasc.
- Name
region- Type
- string
- Description
Beta. Uploader's country as a two-letter code, such as
US. Videos with no known region are left out, and an unknown code returns none.
- Name
intent_match- Type
- boolean
- Description
Data Intelligence agents only.
truekeeps videos that fit your intent, but pages can then come back empty before the end, andtotalcounts only that page. To find every match, page without it and checkintent_match.matches.
- Name
include_transcript- Type
- boolean
- Description
trueadds each video'stranscript: the full text, plus timestamps where the transcript has them. Free. Transcripts make pages several times bigger, so use a smallerlimit. See Transcripts below.
- Name
page / limit- Type
- integer
- Description
From 1, and 1 to 100 per page (default
50, larger values cut to 100).offsetor any unlisted parameter returns400.
Transcripts
With include_transcript=true, every video gets a transcript object:
text: the full transcript.segments:[{ "start": 0.64, "end": 3.52, "text": "..." }], in seconds from the start of the video, ornullwhen there are no timestamps.source:transcribedwhen Virlo turned the audio into text (always timed), orplatformwhen it's the transcript TikTok or YouTube published (usually text only, withsegmentsnull). When both exist you gettranscribed.
transcript is null when the video has no speech, such as music-only videos, or hasn't been transcribed yet.
Where timestamps come from. Most TikTok and YouTube videos have a platform transcript on any agent, usually as text without timestamps. A small share of platform transcripts do carry timestamps, so check segments itself instead of reading it off source. Virlo transcribes the audio itself, with timestamps, only when the platform didn't publish a transcript, and only on agents with Data Intelligence. That makes Instagram Reels Data Intelligence only: Instagram publishes no transcripts, so a Reel's transcript always comes from Virlo, always with timestamps. While a video's intelligence_status is pending, its transcript may still be on the way.
Paging. A page can hold fewer than limit videos while more exist, and total can shift a little between pages. Page until a page is empty, and remove duplicates by id.
To rank by Virality Score, compute it from views and author.followers, as the Research playbook does. Creator outliers can sort by it directly.
Full field reference
publish_dateis UTC with no time zone suffix, such as2026-09-23T14:55:48.durationis the video's length in seconds, ornullwhen the platform didn't report it.sound.durationis the length of the audio track, which can differ.- YouTube usernames start with
@; TikTok and Instagram ones don't.author.countryis the creator's country, oftennull, not the video's upload region. sound.cover_url,thumbnail_url, andauthor.avatar_urlare full links, ornull. Some files are HEIC (.heic), which most browsers other than Safari can't show, so convert them before display.intelligence_statusisready,pending,skipped, ordisabled.skippedmeans Virlo chose not to analyze the video, andintelligence_skip_reasonsays why:intent_mismatch,too_old, orunder_followers(details). It isnullon every other status. Agents with Data Intelligence off can still showreadyon videos analyzed elsewhere, at no charge.upload_region_source:tiktok_region,youtube_channel_country, andinstagram_location_tagare exact;inferred_normalizeis a guess. About 3 in 10 videos have no region, and Instagram videos rarely do.
Request
curl -G https://api.virlo.ai/v1/agents/{agent_id}/videos \
-H "Authorization: Bearer YOUR_API_KEY" \
-d min_views=100000 \
-d start_date=2026-09-01 \
-d region=US \
-d order_by=views \
-d limit=50
Response
{
"data": {
"agent_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"agent_name": "Protein Recipes",
"total": 283,
"limit": 50,
"offset": 0,
"videos": [
{
"id": "e7c2a1b4-9f3d-4e88-8a12-5b6c7d8e9f01",
"url": "https://www.tiktok.com/@fitcoachjen/video/7412345678901234567",
"description": "3-ingredient protein brownies that actually taste good",
"platform": "tiktok",
"views": 2140000,
"likes": 312000,
"shares": 41200,
"comments": 8900,
"bookmarks": 128000,
"publish_date": "2026-09-21T18:22:00",
"duration": 34,
"author": {
"country": "US",
"username": "fitcoachjen",
"verified": false,
"followers": 48200,
"avatar_url": "https://auth.virlo.ai/storage/v1/object/public/avatars/5e1c9a.jpg"
},
"hashtags": ["proteinrecipe", "highprotein", "healthydessert"],
"thumbnail_url": "https://auth.virlo.ai/storage/v1/object/public/thumbnails/9b0f2d.jpg",
"keyword_found_by": "high protein recipe",
"is_duet": false,
"is_stitch": false,
"upload_region": "US",
"upload_region_source": "tiktok_region",
"intelligence": {
"primary_topic": "high-protein dessert recipe",
"content_format": "cooking_recipe",
"hook_type": "tutorial_promise"
},
"intent_match": {
"matches": true,
"reasoning": "The caption and hashtags describe a high-protein dessert recipe, which fits the intent."
},
"sound": {
"id": "8f6e5c50-1451-4007-a120-92744e632dad",
"title": "original sound - fitcoachjen",
"duration": 58,
"cover_url": "https://auth.virlo.ai/storage/v1/object/public/sound-covers/5e1c9a.jpg",
"owner_handle": "fitcoachjen",
"owner_nickname": "Jen",
"is_original": true,
"is_commerce_music": true,
"usage_count": 1,
"platform": "tiktok"
},
"intelligence_status": "ready",
"intelligence_skip_reason": null
}
]
}
}
Get creator outliers
Creators whose videos get far more views than their follower count predicts: small accounts punching above their weight. Free. Ready about a minute after finalized.
For the fairest ranking across account sizes, use order_by=weighted_score. Pass a row's author_id to similar creators to find more like them.
- Name
order_by- Type
- string
- Description
outlier_ratio(default, average views per follower),weighted_score,avg_views,follower_count, orrising(growth since the previous run). With one run, or withcategory,risingfalls back to Virality Score order (ranking: "outlier_fallback").
- Name
sort- Type
- string
- Description
desc(default) orasc.
- Name
platform- Type
- string
- Description
youtube,tiktok, orinstagram. Singular here:platformsreturns400.
- Name
follower_tier- Type
- string
- Description
nano(under 10,000 followers),micro(10,000 to 100,000),mid(100,000 to 1 million), ormacro(over 1 million).
- Name
category- Type
- string
- Description
Keep creators whose topics contain this text.
- Name
page / limit- Type
- integer
- Description
- From 1, and 1 to 100 per page (default
50).
With follower_tier or category, a page can come back empty before the end, and total ignores the filter. Use limit=100 and page while has_more is true.
Details for developers
In videos, id is the platform's own ID, type is the platform, and the date is camelCase publishDate. hasMore is an older duplicate of has_more.
Once the agent has two runs, rising returns ranking: "velocity" and a different shape: rows add growth_followers, growth_views, growth_video_count, and local_video_count, but lack weighted_score and videos, and most outlier stats are null. There is no has_more, and total counts only this page, so page until a page has fewer than limit rows.
Request
curl -G https://api.virlo.ai/v1/agents/{agent_id}/creators/outliers \
-H "Authorization: Bearer YOUR_API_KEY" \
-d order_by=weighted_score \
-d limit=25
Response
{
"data": {
"agent_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"agent_name": "Protein Recipes",
"total": 12,
"limit": 25,
"offset": 0,
"has_more": false,
"hasMore": false,
"outliers": [
{
"author_id": "c3d4e5f6-a7b8-4901-9c2d-3e4f5a6b7c8d",
"creator_url": "https://www.tiktok.com/@fitcoachjen",
"creator_avatar_url": "https://auth.virlo.ai/storage/v1/object/public/avatars/5e1c9a.jpg",
"follower_count": 48200,
"avg_views": 512000,
"outlier_ratio": 10.62,
"weighted_score": 25.48,
"videos_analyzed": 14,
"creator_topics": ["fitness", "recipes", "meal prep"],
"matching_topics": ["recipes", "meal prep"],
"platform": "tiktok",
"identified_at": "2026-09-24T17:31:02.579Z",
"median_views": 431000,
"top_video_views": 2140000,
"breakout_video_count": 4,
"avg_engagement_rate": 0.081,
"posts_per_week": 5.2,
"content_angle": "Three-ingredient high-protein desserts filmed in one unbroken take.",
"videos": [
{
"id": "7412345678901234567",
"title": "",
"description": "3-ingredient protein brownies that actually taste good",
"views": 2140000,
"likes": 312000,
"comments": 8900,
"publishDate": "2026-09-21T18:22:00.000Z",
"url": "https://www.tiktok.com/@fitcoachjen/video/7412345678901234567",
"hashtags": ["proteinrecipe"],
"thumbnail_url": "https://auth.virlo.ai/storage/v1/object/public/thumbnails/9b0f2d.jpg",
"type": "tiktok"
}
]
}
]
}
}
Get latest trends
The trends the AI found in the latest run, ranked, with evidence videos and view and engagement totals: the themes from the analysis. Free.
status is each trend's move since the previous run: new, rising, steady, or fading. A trend going from new to rising with a growing video_count is your strongest "post about this now" signal. stable_key follows a trend across runs in trends history.
Full field reference
avg_virality_score is an AI rating from 0 to 1, not the Virality Score (weighted_score), so don't compare them. peak_hour_utc is the hour (UTC) when the trend's videos do best. The prev_ fields are null on a first run. insight_type holds an older label, here and in the analysis.
Request
curl https://api.virlo.ai/v1/agents/{agent_id}/trends/latest \
-H "Authorization: Bearer YOUR_API_KEY"
Response
{
"data": {
"reference_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"insight_type": "custom_niche",
"viral_insight_id": "d5e6f7a8-b9c0-4d12-8e34-5f6a7b8c9d01",
"batch_start": "2026-09-24T17:23:43.301+00:00",
"batch_end": "2026-09-24T17:29:14.320+00:00",
"total": 5,
"trends": [
{
"id": "aa11bb22-cc33-4d44-8e55-6f778899aa00",
"viral_insight_id": "d5e6f7a8-b9c0-4d12-8e34-5f6a7b8c9d01",
"insight_type": "custom_niche",
"reference_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"batch_start": "2026-09-24T17:23:43.301+00:00",
"batch_end": "2026-09-24T17:29:14.320+00:00",
"rank": 1,
"stable_key": "no-cook-overnight-protein",
"name": "No-cook overnight protein",
"why_it_works": "Zero-effort framing and a high protein payoff in one scroll.",
"tactics": ["show the jar first", "put the protein grams on screen"],
"confidence": 0.86,
"evidence_video_ids": ["e7c2a1b4-9f3d-4e88-8a12-5b6c7d8e9f01"],
"evidence_videos": [
{
"id": "e7c2a1b4-9f3d-4e88-8a12-5b6c7d8e9f01",
"url": "https://www.tiktok.com/@fitcoachjen/video/7412345678901234567",
"platform": "tiktok"
}
],
"video_count": 17,
"total_views": 18400000,
"total_likes": 2100000,
"total_comments": 54000,
"total_shares": 210000,
"avg_virality_score": 0.87,
"platform_breakdown": { "tiktok": 11, "instagram": 4, "youtube": 2 },
"top_creators": [
{ "username": "fitcoachjen", "followers": 48200, "total_views": 2140000, "video_count": 2 }
],
"peak_hour_utc": 18,
"status": "new",
"first_seen_at": "2026-09-24T17:29:14.320+00:00",
"prev_video_count": null,
"prev_total_views": null,
"created_at": "2026-09-24T17:30:17.390296+00:00"
}
]
}
}
Get latest analysis
The AI report on the latest run: the main themes and why they work, tactics to copy, timing, and the top videos. Free.
analysis is the main takeaway, a short paragraph you can quote to a client; analysis_data holds the rest. The AI reads a sample of up to 240 videos, so it covers the strongest patterns, not every video. Before the first analysis, you get { "data": null }.
Full field reference
overview.avg_virality runs from 0 to 1 and is not the Virality Score. excluded_videos lists videos the AI set aside as off-topic. video_count counts the sample, not the whole collection.
Request
curl https://api.virlo.ai/v1/agents/{agent_id}/analysis/latest \
-H "Authorization: Bearer YOUR_API_KEY"
Response
{
"data": {
"id": "d5e6f7a8-b9c0-4d12-8e34-5f6a7b8c9d01",
"insight_type": "custom_niche",
"reference_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"batch_start": "2026-09-24T17:23:43.301+00:00",
"batch_end": "2026-09-24T17:29:14.320+00:00",
"analysis": "No-cook overnight protein recipes are driving the most outsized reach this week...",
"analysis_data": {
"themes": [
{
"name": "No-cook overnight protein",
"tactics": ["show the jar in the first frame", "call out the protein grams in text"],
"confidence": 0.82,
"stable_key": "no-cook-overnight-protein",
"video_count": 17,
"why_it_works": "Zero-effort framing lowers the barrier to trying the recipe.",
"evidence_video_ids": ["e7c2a1b4-9f3d-4e88-8a12-5b6c7d8e9f01"]
}
],
"overview": { "avg_virality": 0.55, "total_videos": 240 },
"key_highlight": "No-cook overnight protein recipes are driving the most outsized reach this week...",
"viral_tactics": ["Put the protein grams on screen in the first second."],
"excluded_videos": [
{ "reason": "A supplement ad, not a recipe.", "video_id": "6e736606-4eb4-434c-9070-4877af24cf56" }
],
"timing_analysis": {
"pattern": "Content performs best in the evening.",
"peak_hours": [19, 20, 21]
},
"whats_happening": [],
"top_10_breakdown": {
"videos": [
{ "video_id": "e7c2a1b4-9f3d-4e88-8a12-5b6c7d8e9f01", "description": "Three-ingredient brownies with the macros on screen." }
],
"intro_header": "Protein Recipes: What's Working Now",
"intro_subheader": "Simple, no-cook recipes with visible macros are winning."
},
"connecting_thread": "Every winning theme makes high protein feel effortless.",
"fresh_insights_headline": "Effortless protein is the breakout format"
},
"video_count": 240,
"analyzed_video_ids": ["e7c2a1b4-9f3d-4e88-8a12-5b6c7d8e9f01"],
"model_used": "gemini-2.5-flash",
"created_at": "2026-09-24T17:29:16.018692+00:00"
}
}
Get sounds
The sounds in the agent's videos, ranked by how many of this agent's videos use each one. Free.
- Name
sort- Type
- string
- Description
Picks the ranking, not a direction:
video_count(default),usage_count(uses across the whole platform, oftennull), orrising(aliasgrowth_7d, the biggest growth since the previous run). Unknown values fall back tovideo_count.
- Name
page / limit- Type
- integer
- Description
- From 1, and 1 to 100 per page (default
20).
lifecycle compares with the previous run: new (not in the previous run, so every sound after a first run), rising or fading (views across this agent's videos using it moved 25% or more), or steady. The growth_ fields stay null until there are two runs.
cover_url is a full link, or null. For one sound's history, pass its id (not external_id) to usage history, $0.05 per request.
Here total and total_pages are estimates. Page while has_next_page is true.
Request
curl -G https://api.virlo.ai/v1/agents/{agent_id}/sounds \
-H "Authorization: Bearer YOUR_API_KEY" \
-d sort=rising \
-d limit=20
Response
{
"data": [
{
"id": "8f6e5c50-1451-4007-a120-92744e632dad",
"external_id": "7412345678901234001",
"title": "Saxophones getting louder",
"platform": "tiktok",
"duration": 30,
"cover_url": "https://auth.virlo.ai/storage/v1/object/public/sound-covers/93b232a4aa7f.jpg",
"owner_handle": "saxsounds",
"owner_nickname": "Sax Sounds",
"is_original": false,
"is_commerce_music": true,
"usage_count": 138106,
"video_count": 42,
"avg_views": 612000,
"growth_video_count": 18,
"growth_views": 240000,
"lifecycle": "rising"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 21,
"total_pages": 2,
"has_next_page": true,
"has_prev_page": false
}
}
Get hashtags
Hashtag stats across the agent's videos: video count, views, engagement, growth between runs, and top creators. Free.
- Name
sort- Type
- string
- Description
Picks the ranking, not a direction:
volume(default),growth(biggest jump since the last run), oravg_views. After one run,growthgives the same order asvolume.
- Name
page / limit- Type
- integer
- Description
- From 1, and 1 to 100 per page (default
50).
order_by and platform return 400 here. lifecycle works as for sounds. total and total_pages are estimates, so page while has_next_page is true.
Request
curl -G https://api.virlo.ai/v1/agents/{agent_id}/hashtags \
-H "Authorization: Bearer YOUR_API_KEY" \
-d sort=growth \
-d limit=25
Response
{
"data": [
{
"hashtag": "proteinrecipe",
"video_count": 96,
"total_views": 41200000,
"avg_views": 429166,
"avg_engagement": 0.081,
"growth_video_count": 34,
"lifecycle": "rising",
"top_creators": [
{
"username": "fitcoachjen",
"avatar_url": "https://auth.virlo.ai/storage/v1/object/public/avatars/5e1c9a.jpg",
"video_count": 8
}
]
}
],
"pagination": {
"page": 1,
"limit": 25,
"total": 26,
"total_pages": 2,
"has_next_page": true,
"has_prev_page": false
}
}
Get hooks
The strongest opening lines (hooks) from the agent's videos, ranked by Virality Score. Parameters and fields: Agent hooks.
Cost: $0.25 per request. Free while the agent has no hooks yet (coverage.videos_with_hooks is 0), and always free when it has Data Intelligence on.
Request
curl -G https://api.virlo.ai/v1/agents/{agent_id}/hooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-d limit=20
Get slideshows
TikTok photo carousels the agent collected with the videos. Free. Almost every slideshow has a region, so region filters work best here.
Same filters as Get videos, but platforms and intent_match do nothing.
Fields that differ from videos
Slideshows have region instead of upload_region, an images list, and an is_eligible_for_commission flag. They have no sound, intent_match, is_duet, is_stitch, or author.country. publish_date ends in +00:00. intelligence uses the slideshow fields.
Request
curl -G https://api.virlo.ai/v1/agents/{agent_id}/slideshows \
-H "Authorization: Bearer YOUR_API_KEY" \
-d region=US \
-d limit=50
Response
{
"data": {
"agent_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"agent_name": "Protein Recipes",
"total": 41,
"limit": 50,
"offset": 0,
"slideshows": [
{
"id": "b7da2c2c-5ff5-4fe3-8e63-dafe51f35314",
"url": "https://www.tiktok.com/@mealprepmaya/photo/7677872045765430561",
"description": "5 high protein breakfasts under 10 minutes",
"platform": "tiktok",
"views": 98900,
"likes": 5190,
"shares": 630,
"comments": 120,
"bookmarks": 3040,
"publish_date": "2026-09-20T07:44:56+00:00",
"author": {
"username": "mealprepmaya",
"verified": false,
"followers": 8979,
"avatar_url": "https://auth.virlo.ai/storage/v1/object/public/avatars/8ead10.jpg"
},
"hashtags": ["mealprep", "highprotein"],
"thumbnail_url": "https://auth.virlo.ai/storage/v1/object/public/thumbnails/cea5f4.jpg",
"images": [
{ "image_url": "https://auth.virlo.ai/storage/v1/object/public/slideshow-images/669187.jpg", "position": 0 }
],
"keyword_found_by": "protein meal prep",
"is_eligible_for_commission": false,
"region": "US",
"intelligence": {
"content_format": "listicle",
"narrative_arc": "listicle",
"text_density": "balanced"
},
"intelligence_status": "ready",
"intelligence_skip_reason": null
}
]
}
}
Get ads
Ads from Meta's Ad Library that the agent collected, when meta_ads_enabled is on. Free. An agent without ads returns an empty list.
- Name
order_by- Type
- string
- Description
created_at(default, when Virlo collected the ad) orpage_like_count(the advertiser page's likes).
- Name
sort- Type
- string
- Description
desc(default) orasc.
- Name
page / limit- Type
- integer
- Description
- From 1, and 1 to 100 per page (default
50).
Request
curl -G https://api.virlo.ai/v1/agents/{agent_id}/ads \
-H "Authorization: Bearer YOUR_API_KEY" \
-d limit=50
Response
{
"data": {
"agent_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"agent_name": "Protein Recipes",
"total": 37,
"limit": 50,
"offset": 0,
"ads": [
{
"id": "56c50beb-9518-463a-9d9b-d8509b94fef6",
"ad_archive_id": "1789791185482522",
"page_id": "120945717945722",
"page_profile_url": "https://www.facebook.com/exampleproteinco/",
"page_profile_picture_url": "https://scontent.xx.fbcdn.net/v/example.jpg",
"is_active": true,
"start_date": "2026-09-15",
"end_date": "2026-09-20",
"url": "https://www.facebook.com/ads/library/?id=1789791185482522",
"caption": "exampleproteinco.com",
"body": "20g of protein in every bar. Try the new flavors.",
"cta_type": "SHOP_NOW",
"page_like_count": 218491,
"title": "New protein bar flavors",
"video_url": null,
"created_at": "2026-09-21T16:23:41.881496+00:00",
"keyword_found_by": "protein snack ideas"
}
]
}
}
List agents
Your agents, newest first, including ones made in the Virlo app, with their full settings. Free.
It is per person, not per team: only agents owned by whoever created your API key. A teammate's agents aren't listed, and reading one by id returns 404.
- Name
is_recurring- Type
- boolean
- Description
truefor recurring agents only,falsefor one-time only.
- Name
include_inactive- Type
- boolean
- Description
Also list paused agents. Deleted agents never appear.
- Name
page / limit- Type
- integer
- Description
From 1, and 1 to 100 per page (default
50).
There is no total. Page until a page has fewer than limit agents.
Request
curl -G https://api.virlo.ai/v1/agents \
-H "Authorization: Bearer YOUR_API_KEY" \
-d is_recurring=true \
-d limit=50
Response
{
"data": {
"limit": 50,
"page": 1,
"count": 1,
"agents": [
{
"id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"name": "Protein Recipes",
"is_recurring": true,
"active": true,
"team_id": "9d4c2b10-8e6f-4a23-b1c7-0a5e3f9d2b18",
"source": "api",
"keywords": ["high protein recipe", "protein meal prep", "protein snack ideas"],
"platforms": ["youtube", "tiktok", "instagram"],
"exclude_keywords": ["powder", "supplement"],
"exclude_keywords_strict": false,
"meta_ads_enabled": true,
"data_intelligence_enabled": false,
"english_only": true,
"intent": "Track viral protein-recipe content for a fitness brand",
"intent_keywords": ["high protein recipe ideas", "protein meal prep for the week", "easy protein snacks"],
"autopilot": true,
"autonomy_level": "autopilot",
"autopilot_unlocked": true,
"cognition_enabled": true,
"pinned_keywords": ["high protein recipe", "protein meal prep", "protein snack ideas"],
"cadence": "0 0 * * 0",
"next_run_at": "2026-09-27T02:14:08.000Z",
"last_run_at": "2026-09-24T17:31:12.491Z",
"is_processing": false,
"created_at": "2026-09-24T17:23:43.255Z",
"updated_at": "2026-09-24T17:31:12.525Z"
}
]
}
}
Update agent
Changes settings for future runs. Send only what you want to change. Free. Videos already collected are not re-filtered.
There is no "run now" call: to research again, create a new one-time agent. Autopilot never adds a billed run either. An agent can't switch type: sending is_recurring returns 400 (property is_recurring should not exist).
- Name
active- Type
- boolean
- Description
falsepauses a recurring agent: no runs, no charges.trueresumes it.
- Name
name / intent / platforms- Type
- mixed
- Description
- Replace the current values, with the same rules as Create agent.
- Name
keywords- Type
- string[]
- Description
- Replaces the list, with the same rules as Create agent. On an agent made through the API, your new list becomes the pinned set that autopilot always keeps.
- Name
cadence- Type
- string
- Description
- Recurring agents only. On a one-time agent it returns
400(cadence can only be set on a recurring agent).
- Name
exclude_keywords- Type
- string[]
- Description
- Replaces the list. On an agent made through the API, autopilot keeps every word you send. Clear it (
[]) and the next run picks new words from yourintent.
- Name
exclude_keywords_strict / meta_ads_enabled / english_only- Type
- boolean
- Description
- Turn these on or off.
- Name
data_intelligence_enabled- Type
- boolean
- Description
- Turning it on makes each later run $1.50.
- Name
autopilot- Type
- boolean
- Description
trueturns autopilot on.falseturns it off and keeps the setup exactly as you set it.
Request
curl -X PUT https://api.virlo.ai/v1/agents/{agent_id} \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "keywords": ["high protein recipe", "protein meal prep", "protein desserts"] }'
Response
{
"data": {
"id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"name": "Protein Recipes",
"is_recurring": true,
"active": true,
"keywords": ["high protein recipe", "protein meal prep", "protein desserts"],
"intent_keywords": null,
"autopilot": true,
"pinned_keywords": ["high protein recipe", "protein meal prep", "protein desserts"],
"cadence": "0 0 * * 0",
"next_run_at": "2026-09-27T02:14:08.000Z",
"updated_at": "2026-09-24T18:02:11.684Z"
},
"message": "Agent updated"
}
Delete agent
Deletes the agent, so it never runs or charges again. Returns 204, also on a repeat call. This can't be undone. To stop it for now, pause it with Update agent.
A deleted agent disappears from List agents, even with include_inactive=true. Reading it by id may work for a while, showing active: false, but save what you need first.
Request
curl -X DELETE https://api.virlo.ai/v1/agents/{agent_id} \
-H "Authorization: Bearer YOUR_API_KEY"
More reads
Less common questions, all free.
Get benchmarks
GET /v1/agents/:id/benchmarks: what's normal for creators in this niche by account size, so you can see whether a creator beats others their size. One row per follower tier, largest first. Empty tiers are left out.
Fields and example
- Name
follower_tier- Type
- string
- Description
The tiers from creator outliers, or
unknown(no follower count, somedian_followersandmedian_posting_frequency_daysarenull).
- Name
median_engagement_rate- Type
- number
- Description
A decimal (
0.071means 7.1%). Compare creators only within a tier. A smallcreator_countmakes it noisy.
- Name
median_videos_in_niche- Type
- integer
- Description
This agent's videos per creator. Often
1.
- Name
median_posting_frequency_days- Type
- number
- Description
Days between videos in this agent's collection, counting only creators with at least two. Not their overall posting rate, and
nullwhen no creator in the tier has two.
curl https://api.virlo.ai/v1/agents/{agent_id}/benchmarks \
-H "Authorization: Bearer YOUR_API_KEY"
{
"data": [
{
"follower_tier": "macro",
"creator_count": 21,
"median_engagement_rate": 0.0294,
"median_followers": 2130000,
"median_videos_in_niche": 1,
"median_posting_frequency_days": 242.43
},
{
"follower_tier": "micro",
"creator_count": 48,
"median_engagement_rate": 0.071,
"median_followers": 42000,
"median_videos_in_niche": 1,
"median_posting_frequency_days": 1.4
}
]
}
Get affinity
GET /v1/agents/:id/affinity: the topics this niche's creators cover most, and the top sounds and hashtags in the agent's videos, up to 20 of each. Beta: a rough guide. For rankings and growth, use sounds and hashtags.
Example
curl https://api.virlo.ai/v1/agents/{agent_id}/affinity \
-H "Authorization: Bearer YOUR_API_KEY"
{
"data": {
"creator_topics": [
{ "topic": "meal prep", "creator_count": 31 }
],
"related_sounds": [
{
"title": "Saxophones getting louder",
"artist": "saxsounds",
"sound_id": "8f6e5c50-1451-4007-a120-92744e632dad",
"video_count": 42
}
],
"related_hashtags": [
{ "hashtag": "proteinrecipe", "video_count": 96 }
]
}
}
Get similar creators
GET /v1/agents/:id/creators/:creator_id/similar: other creators in this agent's videos who share the most hashtags and sounds with one creator. Beta: a rough guide. For creator_id, use an author_id from creator outliers or from this endpoint's rows (video rows have none). A creator_id not in this agent returns an empty list, not an error.
Parameters and example
- Name
limit- Type
- integer
- Description
- 1 to 100. Default
20. There is no paging:pagereturns400.
curl -G https://api.virlo.ai/v1/agents/{agent_id}/creators/{creator_id}/similar \
-H "Authorization: Bearer YOUR_API_KEY" \
-d limit=20
{
"data": [
{
"author_id": "40dc6179-5bea-4797-8c15-5c78ee0b3d1a",
"username": "proteinpantry",
"avatar_url": "https://auth.virlo.ai/storage/v1/object/public/avatars/2a7f11.jpg",
"url": "https://www.tiktok.com/@proteinpantry",
"verified": false,
"followers": 5510,
"shared_hashtag_count": 3,
"shared_sound_count": 1,
"similarity_score": 4
}
]
}
Get trends history
GET /v1/agents/:id/trends: every trend this agent has produced, newest first, shaped like latest trends. Filter by stable_key to follow one trend across runs.
Parameters and example
- Name
stable_key- Type
- string
- Description
- Show only one trend's history.
- Name
start_date / end_date- Type
- string
- Description
- Only trends in this window.
- Name
page / limit- Type
- integer
- Description
- From 1, and 1 to 100 per page (default
50).
The pagination object's total is a real count.
curl -G https://api.virlo.ai/v1/agents/{agent_id}/trends \
-H "Authorization: Bearer YOUR_API_KEY" \
-d stable_key=no-cook-overnight-protein \
-d limit=50
Get analysis history
GET /v1/agents/:id/analysis: every analysis this agent has produced, newest first, shaped like latest analysis.
Parameters and example
- Name
start_date / end_date- Type
- string
- Description
- Only analyses in this window.
- Name
page / limit- Type
- integer
- Description
- From 1, and 1 to 100 per page (default
50).
The pagination object's total is a real count.
curl -G https://api.virlo.ai/v1/agents/{agent_id}/analysis \
-H "Authorization: Bearer YOUR_API_KEY" \
-d limit=20
List runs
GET /v1/agents/:id/runs: the agent's runs, newest first. Each is a collection report: how many videos came in, how many were dropped, and why. When an agent returns less than you expected, start here.
Parameters, fields, and example
- Name
page / limit- Type
- integer
- Description
- From 1, and 1 to 100 per page (default
50). Other parameters, such asstatus, are ignored.
There is no total, and the response shows offset, not page. Page until a page has fewer than limit runs. On a partial_failure run the counts can read 0 even though videos came in, so check total on Get videos.
keyword_breakdown: results per phrase actually searched, the best field for tuning keywords.intent_filtered,language_filtered_count,exclude_keywords_filtered: videos dropped by yourintent, byenglish_only, and by your excludes. A highintent_filteredmeans your keywords pull in off-topic videos.youtube_count,tiktok_count,instagram_count: what each platform returned, before filters, so they can add up to more thanvideos_linked.total_videos_insertedandtotal_videos_updated: videos new to Virlo, and videos Virlo already had, now refreshed.duplicates_droppedandtrends_detectedare currently always0andnull.
curl -G https://api.virlo.ai/v1/agents/{agent_id}/runs \
-H "Authorization: Bearer YOUR_API_KEY" \
-d limit=50
{
"data": {
"agent_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"limit": 50,
"offset": 0,
"count": 1,
"runs": [
{
"id": "5c8b1f20-3a4d-4e7f-9b12-6d2a1c7e8f90",
"agent_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
"status": "completed",
"total_videos_inserted": 122,
"total_videos_updated": 161,
"total_videos_failed": 0,
"videos_linked": 283,
"meta_ads_linked": 37,
"slideshows_linked": 41,
"youtube_count": 104,
"tiktok_count": 171,
"instagram_count": 33,
"exclude_keywords_filtered": 14,
"intent_filtered": 38,
"language_filtered_count": 24,
"duplicates_dropped": 0,
"outliers_identified": 12,
"trends_detected": null,
"execution_time_ms": 447662,
"created_at": "2026-09-24T17:23:43.349Z",
"started_at": "2026-09-24T17:23:43.423Z",
"completed_at": "2026-09-24T17:31:11.085Z",
"keyword_breakdown": [
{
"keyword": "high protein recipe ideas",
"tiktok_count": 76,
"videos_linked": 132,
"youtube_count": 54,
"videos_updated": 94,
"instagram_count": 13,
"videos_inserted": 38
}
]
}
]
}
}
Get run
GET /v1/agents/:id/runs/:run_id: one run, the same object as in List runs. A run from a different agent returns 404 (Run not found).
Coming from Orbit or Comet?
Orbit and Comet were the old names for one-time and recurring agents. Their URLs still work but are deprecated, so move now. Agent IDs carry over, so it's mostly a URL change: Orbit · Comet.
In insight_type, orbit still means a one-time agent and custom_niche a recurring one.
