# Workflow recipes

> Step-by-step code for common research questions: what works in a niche, why a creator's video took off, what is trending, which hashtags are big, and what works in another language or country. Each recipe lists its cost and how long it takes.

Source: https://dev.virlo.ai/docs/recipes
Markdown: https://dev.virlo.ai/docs/recipes.md
Section: How the API works

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

---

Code for five research questions, with the cost and time of each. Replace `YOUR_API_KEY` with [your key](https://dev.virlo.ai/docs/quickstart).

**At a glance**

- **What it does:** Answers one research question per recipe, call by call.
- **You send:** A topic, a creator's handle, or a hashtag.
- **You get back:** An AI report, top videos, ads, standout creators, trends, or hashtag stats.
- **Cost:** $0.15 to $1.25 per recipe as written, before add-ons. A recurring agent then costs $0.50 per run until you pause or delete it. Reading an agent's videos, ads, creators, and report is free; hashtag and trend lists charge every time. A successful response's `X-Cost` header shows its cost.

An **agent** (Content Research Agent) collects matching videos and writes an AI report, once or on a schedule. A **lookup** checks one creator, video, sound, or hashtag.

- [What works in my niche?](https://dev.virlo.ai/docs/recipes#full-niche-analysis)
- [Why did a creator's video take off?](https://dev.virlo.ai/docs/recipes#creator-deep-dive)
- [What is trending, and can I keep watching it?](https://dev.virlo.ai/docs/recipes#trend-monitoring-setup)
- [Which hashtags are biggest?](https://dev.virlo.ai/docs/recipes#hashtag-research)
- [What works in Spanish, or in Mexico?](https://dev.virlo.ai/docs/recipes#global-non-english-research)

---

## Full niche analysis

A one-time agent searches TikTok, YouTube, Instagram, and Meta ads, then reports what is working.

**At a glance**

- **Cost:** $0.50, charged when you create the agent, even if the run fails. $1.50 with [Data Intelligence](https://dev.virlo.ai/docs/intelligence): AI notes on hook and format for many, not all, videos.
- **How long:** Half of all runs are done in under 8 minutes, and 9 in 10 in under 20. Broad runs like this take longer.

### 1. Create a one-time agent ($0.50)

Describe what you want (`intent`) and add keywords, or get them free from [Suggest keywords](https://dev.virlo.ai/docs/agents#suggest-keywords).

**cURL:**

```bash
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": "Find what is working in Jeep Wrangler mods content right now",
    "name": "Jeep Wrangler Mods Research",
    "keywords": ["jeep wrangler mods", "jeep wrangler accessories", "jeep upgrades"],
    "platforms": ["youtube", "tiktok", "instagram"],
    "meta_ads_enabled": true
  }'
```

**Python:**

```python
import requests, time

# is_recurring=False: run once. name is a label for you.
# meta_ads_enabled also collects Meta ads (Facebook and Instagram ads).
# No min_views or date range here: filter for free when you read results (step 3).
response = requests.post(
    'https://api.virlo.ai/v1/agents',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    json={
        'is_recurring': False,
        'intent': 'Find what is working in Jeep Wrangler mods content right now',
        'name': 'Jeep Wrangler Mods Research',
        'keywords': ['jeep wrangler mods', 'jeep wrangler accessories', 'jeep upgrades'],
        'platforms': ['youtube', 'tiktok', 'instagram'],
        'meta_ads_enabled': True
    }
)
agent_id = response.json()['data']['id']  # returned right away; the research runs in the background
```

### 2. Wait until it's done (free)

[Check again](https://dev.virlo.ai/docs/async-data) every 30 seconds until `finalized` is `true` (report written) and `last_run_at` is set (creators scored). A `partial_failure` run (one keyword or platform failed) is still usable; a `failed` run is not.

**Python:**

```python
deadline = time.time() + 60 * 60  # stop waiting after an hour (rare)
while True:
    agent = requests.get(
        f'https://api.virlo.ai/v1/agents/{agent_id}',
        headers={'Authorization': 'Bearer YOUR_API_KEY'}
    ).json()['data']

    if agent['latest_run']['status'] == 'failed':
        raise Exception('The run failed.')
    # finalized: videos collected and the AI report written
    #   (latest_run.status "completed" alone only means videos are collected).
    # last_run_at: set about a minute later, when creator scoring is done.
    if agent['finalized'] and agent['last_run_at']:
        break
    if time.time() > deadline:
        # Check again later. Creating a new agent would charge you again.
        raise TimeoutError('Still running after an hour.')

    print(f"{agent['stage']}: {agent['progress_pct']}%")  # e.g. "collecting: 40%"
    time.sleep(30)

# analysis is only the headline. The full report (themes, tactics, posting times,
# top 10 videos) is in analysis_data, also at GET /v1/agents/:id/analysis/latest.
report = agent['analysis_data'] or {}
print(agent['analysis'])  # the main takeaway, in one or two sentences
for theme in report.get('themes', []):
    print(f"- {theme['name']}: {theme['why_it_works']}")

# Example output, from a latte art agent:
# The most impactful insight for creators is the overwhelming success of 'progress journey' content, ...
# - Progress Journey Latte Art: Documenting daily or incremental improvements creates a relatable narrative, ...
```

Or use the `content_research_agent.run.completed` [webhook](https://dev.virlo.ai/docs/webhooks#supported-events). It can arrive up to a minute before creator scoring ends, so check `last_run_at` first.

### 3. Read the results (free)

Standout creators get far more views than their follower count predicts.

**Python:**

```python
# Free, including the filters (min_views, start_date, end_date, platforms, region).

# The 50 most-viewed videos
videos = requests.get(
    f'https://api.virlo.ai/v1/agents/{agent_id}/videos',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    params={'limit': 50, 'order_by': 'views', 'sort': 'desc'}
).json()['data']['videos']

# Meta ads (needs meta_ads_enabled at create time)
ads = requests.get(
    f'https://api.virlo.ai/v1/agents/{agent_id}/ads',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    params={'limit': 50}
).json()['data']['ads']

# Standout creators ("creator outliers"), best Virality Score first.
# weighted_score = ln(avg_views / followers) x ln(followers)
# 35+ exceptional, 25-35 very strong, 18-25 strong, 10-18 promising
outliers = requests.get(
    f'https://api.virlo.ai/v1/agents/{agent_id}/creators/outliers',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    params={'limit': 20, 'order_by': 'weighted_score', 'sort': 'desc'}
).json()['data']['outliers']

for creator in outliers[:5]:
    print(f"{creator['platform']}: {creator['follower_count']:,} followers, "
          f"{creator['avg_views']:,.0f} avg views, score {creator['weighted_score']}")
    print(f"  {creator['creator_url']}")

# Example output:
# instagram: 1,791 followers, 475,807 avg views, score 41.81
#   https://www.instagram.com/...
```

### 4. Optional: profile a creator ($0.50 to $1.00)

Run the [Creator deep dive](https://dev.virlo.ai/docs/recipes#creator-deep-dive) on their handle ($0.50, or $1.00 with its step 2). TikTok and Instagram `creator_url`s end with the handle; on YouTube, open the link and copy the @handle.

**Python:**

```python
for creator in outliers[:3]:
    handle = creator['creator_url'].rstrip('/').split('/')[-1].lstrip('@')
    # On YouTube this is often a channel ID (/channel/UC...), not a handle.
    print(creator['platform'], handle)  # tiktok outside.kev
```

---

## Creator deep dive

Size up a creator, then see if their top recent video is a real breakout.

**At a glance**

- **Cost:** $1.00: $0.50 per lookup, charged when it starts. A failed creator lookup, such as for a handle that doesn't exist, is refunded; a failed video check isn't.
- **How long:** About a minute. Each lookup usually takes 20 to 40 seconds.

### 1. Look up the creator ($0.50)

**cURL:**

```bash
curl "https://api.virlo.ai/v1/satellite/creator/tiktok/khaby.lame?include=videos,outliers&max_videos=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Python:**

```python
import requests, time

response = requests.get(
    'https://api.virlo.ai/v1/satellite/creator/tiktok/khaby.lame',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    # include adds their recent videos and standout ones. max_videos: how many to read (up to 100).
    params={'include': 'videos,outliers', 'max_videos': 20}
)
creator_job_id = response.json()['data']['job_id']

# Check the job every 10 seconds until it is completed or failed.
while True:
    result = requests.get(
        f'https://api.virlo.ai/v1/satellite/creator/status/{creator_job_id}',
        headers={'Authorization': 'Bearer YOUR_API_KEY'}
    ).json()['data']

    if result['status'] == 'completed':
        break
    if result['status'] == 'failed':
        raise Exception(result['error'])  # e.g. the handle has no public posts
    time.sleep(10)

creator = result['result']
print(f"Followers: {creator['profile']['followers']}")
print(f"Avg views: {creator['stats']['avg_views']}")
print(f"Engagement rate: {creator['stats']['engagement_rate']:.0%}")  # 0.06 means 6%
```

### 2. Check their top recent video ($0.50)

This checks the most-viewed of the 20 recent videos the lookup read, not necessarily their all-time top.

**Python:**

```python
# The most-viewed of the recent videos the lookup read
top_video = max(creator['videos'], key=lambda v: v['views'])

response = requests.post(
    'https://api.virlo.ai/v1/satellite/video-outlier',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    json={'url': top_video['url'], 'platform': 'tiktok'}
)
video_job_id = response.json()['data']['job_id']

while True:
    result = requests.get(
        f'https://api.virlo.ai/v1/satellite/video-outlier/status/{video_job_id}',
        headers={'Authorization': 'Bearer YOUR_API_KEY'}
    ).json()['data']

    if result['status'] == 'completed':
        break
    if result['status'] == 'failed':
        raise Exception(result['error'])
    time.sleep(10)

analysis = result['result']['analysis']
print(f"Outlier score: {analysis['outlier_score']}")    # views / the creator's median views
print(f"Percentile: {analysis['percentile']}")          # % of their recent videos it beat
print(f"Label: {analysis['performance_label']}")        # average | above_average | viral | mega_viral

# Both results are saved. Reopen either for free with GET /v1/satellite/runs/{job_id},
# so keep both IDs. A new paid lookup of the same creator gets its own ID; old runs stay.
```

`outlier_score` is views divided by the creator's median views (up to 100 recent videos): `mega_viral` is 10x or more, `viral` 3 to 10x, `above_average` 1.5 to 3x, `average` under 1.5x.

---

## Trend monitoring setup

Find today's trends, research one, then keep watching it with a recurring agent.

**At a glance**

- **Cost:** $1.25 to start ($0.25 trends, $0.50 one-time agent, $0.50 first recurring run), or $0.75 without the one-time agent. Then $0.50 per run.
- **How long:** Seconds, plus one agent run (up to about 20 minutes).

### 1. Get today's trends ($0.25)

Worldwide trends, unless you add `region=us` (or `gb`, `au`, `sg`).

**Python:**

```python
import requests

API = 'https://api.virlo.ai/v1'
headers = {'Authorization': 'Bearer YOUR_API_KEY'}

digest = requests.get(f'{API}/trends/digest', headers=headers).json()['data']

for group in digest:
    print(group['local_date'])  # the list's date. Don't use 'title', which can be a day off.
    for item in group['trends']:
        print(f"  #{item['ranking']}: {item['trend']['name']}")
```

### 2. Research one trend ($0.50)

Get free keyword ideas and start a one-time agent, then read results as in the [Full niche analysis](https://dev.virlo.ai/docs/recipes#full-niche-analysis).

**Python:**

```python
trend_name = digest[0]['trends'][0]['trend']['name']  # or pick one from the list
intent = f'Find what is working in short videos about {trend_name}'

# Keyword ideas (free)
suggestion = requests.post(
    f'{API}/agents/suggest-keywords',
    headers=headers,
    json={'intent': intent}
).json()['data']
# Before you pay for a run, check that suggestion['quality']['passes'] is True.

# One-time agent ($0.50). Optional: skip it to start at $0.75, but keep the keyword ideas.
agent = requests.post(
    f'{API}/agents',
    headers=headers,
    json={
        'is_recurring': False,
        'intent': intent,
        'keywords': suggestion['keywords'],
        'exclude_keywords': suggestion['exclude_keywords'],  # words that point to other topics
    }
).json()['data']
agent_id = agent['id']
```

### 3. Keep watching it ($0.50 per run)

Create a new agent with `is_recurring: true` and a `cadence`: `daily`, `weekly`, `monthly`, or a custom schedule (cron format), at most once a day.

Creating it is free, but its first run starts at once. Each run costs $0.50 when it finishes. A `partial_failure` run is charged; a `failed` run isn't. These charges aren't in any header, so [check your balance](https://dev.virlo.ai/docs/credits#check-your-balance). To stop them, pause ([Update agent](https://dev.virlo.ai/docs/agents#update-agent), `active: false`) or [delete](https://dev.virlo.ai/docs/agents#delete-agent) the agent.

**Python:**

```python
# Your balance must cover the first run ($0.50), or this returns a 402 error.
monitor = requests.post(
    f'{API}/agents',
    headers=headers,
    json={
        'is_recurring': True,
        'cadence': 'weekly',  # runs early each Sunday, UTC
        'name': f'Weekly watch: {trend_name}',
        'intent': intent,
        'keywords': suggestion['keywords'],
        'exclude_keywords': suggestion['exclude_keywords'],
    }
).json()['data']

print(f"Agent ID: {monitor['id']}")
print(f"Next scheduled run: {monitor['next_run_at']}")  # the first run has already started
```

---

## Hashtag research

See which hashtags had the most-viewed videos posted in the last 30 days.

**At a glance**

- **Cost:** $0.05 per call: $0.15 for steps 1 to 3, or $0.25 adding YouTube and Instagram. Empty lists are billed; a hashtag with no videos (`404`) is free.
- **How long:** A few seconds per call.

Numbers count only videos Virlo has collected, so compare [hashtags](https://dev.virlo.ai/docs/hashtags) with each other, not with platform totals.

### 1. Top hashtags ($0.05)

Top lists need both dates (`YYYY-MM-DD`, at most 90 days apart). Views are the current totals of videos posted in that range.

**Python:**

```python
import requests
from datetime import date, timedelta

headers = {'Authorization': 'Bearer YOUR_API_KEY'}
end = date.today().isoformat()
start = (date.today() - timedelta(days=30)).isoformat()

hashtags = requests.get(
    'https://api.virlo.ai/v1/hashtags',
    headers=headers,
    params={'start_date': start, 'end_date': end, 'limit': 20, 'order_by': 'views', 'sort': 'desc'}
).json()['data']

# Generic tags like #fyp lead. Hashtags come back lowercase, without the #.
for tag in hashtags[:5]:
    print(f"#{tag['hashtag']}: {tag['count']:,} videos, {tag['total_views']:,} views")
```

### 2. One hashtag's numbers ($0.05)

Skip the dates for all-time numbers.

**Python:**

```python
perf = requests.get(
    'https://api.virlo.ai/v1/hashtags/fyp/performance',  # no # in the path
    headers=headers,
    params={'start_date': start, 'end_date': end}
).json()['data']

print(f"#fyp: {perf['video_count']:,} videos, {perf['total_views']:,} views, avg {perf['avg_views']:,.0f} views/video")
```

### 3. Top hashtags on one platform ($0.05)

**Python:**

```python
# YouTube and Instagram: /v1/youtube/hashtags and /v1/instagram/hashtags
tiktok = requests.get(
    'https://api.virlo.ai/v1/tiktok/hashtags',
    headers=headers,
    params={'start_date': start, 'end_date': end, 'limit': 10, 'order_by': 'views', 'sort': 'desc'}
).json()['data']

for tag in tiktok[:3]:
    print(f"#{tag['hashtag']}: {tag['count']:,} videos, {tag['total_views']:,} views")

# Example output (videos posted Aug 25 to Sep 23, 2026):
# #fyp: 31,049 videos, 5,162,705,739 views
# #viral: 9,971 videos, 1,693,054,907 views
# #funny: 4,276 videos, 1,242,478,023 views
```

For a hashtag's videos, use a [hashtag lookup](https://dev.virlo.ai/docs/satellite/hashtags) ($0.50 to $2.50).

---

## Research in another language or country

Research a niche in any language, then narrow results to one country.

**At a glance**

- **Cost:** $0.50, charged when you create the agent ($1.50 with Data Intelligence). Country filters are free.
- **How long:** The same as a [Full niche analysis](https://dev.virlo.ai/docs/recipes#full-niche-analysis).

1. **Set `english_only: false`** when you create the agent, or it keeps only English. Write the name, intent, and keywords in your language; Virlo builds its search terms in that language. English may still appear (there's no language filter).
2. **`region`** when you read results: a two-letter code like `MX`, any case. `Mexico` returns no results, not an error.

> **Note:** **Country data is in beta.** Items with no known country are left out of filtered results. TikTok slideshows (swipeable photo posts) almost always have one; Instagram videos rarely do.

### 1. Create an all-languages agent ($0.50)

**cURL:**

```bash
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": "Investigar el contenido viral de fútbol en Latinoamérica",
    "name": "Fútbol LATAM",
    "keywords": ["goles de fútbol", "jugadas de fútbol", "resumen de partido"],
    "platforms": ["tiktok", "instagram"],
    "english_only": false
  }'
```

**Python:**

```python
import requests, time

resp = requests.post(
    'https://api.virlo.ai/v1/agents',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    json={
        'is_recurring': False,
        'intent': 'Investigar el contenido viral de fútbol en Latinoamérica',
        'name': 'Fútbol LATAM',
        'keywords': ['goles de fútbol', 'jugadas de fútbol', 'resumen de partido'],
        'platforms': ['tiktok', 'instagram'],
        'english_only': False,  # keep every language
    },
)
agent_id = resp.json()['data']['id']
```

### 2. Wait, then filter by country (free)

Videos carry the country as `upload_region`, slideshows as `region`; both are `null` when unknown.

**cURL:**

```bash
# Videos uploaded from Mexico
curl -G https://api.virlo.ai/v1/agents/{agent_id}/videos \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d region=MX \
  -d order_by=views \
  -d sort=desc \
  -d limit=50

# Slideshows uploaded from Argentina
curl -G https://api.virlo.ai/v1/agents/{agent_id}/slideshows \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d region=AR \
  -d limit=50
```

**Python:**

```python
# First wait until finalized, with the Full niche analysis polling code
# (you can skip its last_run_at check, since this recipe doesn't read creators).
headers = {'Authorization': 'Bearer YOUR_API_KEY'}

mx_videos = requests.get(
    f'https://api.virlo.ai/v1/agents/{agent_id}/videos',
    headers=headers,
    params={'region': 'MX', 'order_by': 'views', 'sort': 'desc', 'limit': 50},
).json()['data']['videos']

ar_slideshows = requests.get(
    f'https://api.virlo.ai/v1/agents/{agent_id}/slideshows',
    headers=headers,
    params={'region': 'AR', 'limit': 50},
).json()['data']['slideshows']

for v in mx_videos:
    print(v['upload_region'], v['views'], v['url'])   # videos: upload_region
for s in ar_slideshows:
    print(s['region'], s['views'], s['url'])          # slideshows: region
```

---

More in How the API works:

- [How results load](https://dev.virlo.ai/docs/async-data.md)
- [Pagination](https://dev.virlo.ai/docs/pagination.md)
- [Rate limits](https://dev.virlo.ai/docs/rate-limits.md)
- [Webhooks](https://dev.virlo.ai/docs/webhooks.md)
- [Errors](https://dev.virlo.ai/docs/errors.md)
- [API playground](https://dev.virlo.ai/docs/playground.md)
