# Content Research Agents

> Research a niche once or on a schedule. One call creates the agent. After it runs, you read its videos, creators, trends, and analysis, almost all of it for free.

Source: https://dev.virlo.ai/docs/agents
Markdown: https://dev.virlo.ai/docs/agents.md
Section: Content Research Agents

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

---

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.

**At a glance**

- **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](https://dev.virlo.ai/docs/quickstart)), a one-sentence `intent`, some keywords, and whether it runs once or on a schedule.
- **You get back:** An agent `id` right away. Once the run is done, you read its videos, creators, trends, and report, all free except [hooks](https://dev.virlo.ai/docs/agents#get-agent-hooks).
- **Cost:** $0.50 per run, $1.50 with [Data Intelligence](https://dev.virlo.ai/docs/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

1. **Get keywords (free).** Send your `intent`, one sentence about what you want, to [`POST /v1/agents/suggest-keywords`](https://dev.virlo.ai/docs/agents#suggest-keywords).
2. **Create the agent.** Send that `intent`, the `keywords`, and any `exclude_keywords` you agree with to [`POST /v1/agents`](https://dev.virlo.ai/docs/agents#create-agent). You get its `id` at once.
3. **Wait** until [`GET /v1/agents/:id`](https://dev.virlo.ai/docs/agents#get-agent) shows `finalized: true`. Check every 15 seconds, or whatever `retry_after_seconds` says. This polling is free. Or use the [`content_research_agent.run.completed`](https://dev.virlo.ai/docs/webhooks#supported-events) webhook.
4. **Read the results.** Start with the [summary](https://dev.virlo.ai/docs/agents#get-agent-summary), then [videos](https://dev.virlo.ai/docs/agents#get-agent-videos), [creator outliers](https://dev.virlo.ai/docs/agents#get-agent-outliers) (ready about a minute after `finalized`), and [trends](https://dev.virlo.ai/docs/agents#get-agent-trends).

**Virality Score** (`weighted_score`, on creator and hook rows) shows how far views outran follower count, with extra credit for bigger accounts ([formula](https://dev.virlo.ai/docs/agent-playbook#spotting-the-most-viral)). 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](https://dev.virlo.ai/docs/agents#get-agent-summary) |
| Which videos did best? | [Get videos](https://dev.virlo.ai/docs/agents#get-agent-videos) with `order_by=views` |
| Why is it working? | [Get latest trends](https://dev.virlo.ai/docs/agents#get-agent-trends), [Get latest analysis](https://dev.virlo.ai/docs/agents#get-agent-analysis) |
| Which creators should we work with? | [Get creator outliers](https://dev.virlo.ai/docs/agents#get-agent-outliers), then [similar creators](https://dev.virlo.ai/docs/agents#get-agent-similar-creators) and [benchmarks](https://dev.virlo.ai/docs/agents#get-agent-benchmarks) |
| Which sounds, hashtags, and hooks? | [Get sounds](https://dev.virlo.ai/docs/agents#get-agent-sounds), [Get hashtags](https://dev.virlo.ai/docs/agents#get-agent-hashtags), [Get hooks](https://dev.virlo.ai/docs/agents#get-agent-hooks) |
| Why so few videos? | [List runs](https://dev.virlo.ai/docs/agents#list-agent-runs) |
| What did autopilot change? | [Get activity](https://dev.virlo.ai/docs/agents/autopilot#get-agent-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](https://dev.virlo.ai/docs/intent-cookbook).

---

## Suggest keywords

**Endpoint:** `POST https://api.virlo.ai/v1/agents/suggest-keywords`

Turns your `intent` into 7 to 12 graded keywords plus words to exclude, ready for [Create agent](https://dev.virlo.ai/docs/agents#create-agent). Free, and it creates nothing.

- `intent` (string, required): One sentence, up to 500 characters. Reuse it for Create agent.
- `topic_hint` (string, optional): A short topic name to steer the result.
- `platforms` (string[], optional): `youtube`, `tiktok`, `instagram`. Not `meta_ads`.
- `mode` (string, optional): `create` (default) builds a fresh list, `refresh` replaces keywords that stopped finding much, `opportunity` suggests new angles. Apply the result with [Update agent](https://dev.virlo.ai/docs/agents#update-agent).
- `existing_keywords` (string[], optional): Current keywords, for `refresh` or `opportunity`.
- `desired_count` (integer, optional): Accepts 1 to 50, but you always get 7 to 12.
- `use_web_grounding` (boolean, optional): 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.

**cURL request:**

```bash
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 200:**

```json
{
  "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

**Endpoint:** `POST https://api.virlo.ai/v1/agents`

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-Cost` shows `0.50` (or `1.50`). No refund if the run fails.
- **Recurring:** free to create (the `X-Cost` response header says `0.00`). Each run, the first starting right away, is charged when it finishes. Charges show on your [Usage page](https://dev.virlo.ai/dashboard/usage), not in a header. Failed runs are free; `partial_failure` runs (one keyword or platform failed) cost full price. Runs repeat until you 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.
- Your [balance](https://dev.virlo.ai/dashboard/billing) must cover the first run, or you get `402` (`insufficient_credits`).
- `is_recurring` (boolean, required): `false` runs once, `true` repeats on `cadence`. Can't be changed later. The string `"false"` is rejected.
- `intent` (string, required): Up to 500 characters. See [Writing a good intent](https://dev.virlo.ai/docs/agents#writing-a-good-intent).
- `keywords` (string[], required): 1 to 50 search phrases; 7 to 12 multi-word phrases work best. A leading `#` is dropped (`#latteart` searches `latteart`), so write `latte art` for the phrase. The agent may search refined versions, shown in `intent_keywords`.
- `cadence` (string, optional): 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 back `null`.
- `platforms` (string[], optional): `youtube`, `tiktok`, `instagram`. Defaults to all three.
- `name` (string, optional): Your label, or `null` if you leave it out.
- `exclude_keywords` (string[], optional): Up to 100 single words, saved in lowercase, for the _other_ meaning of your topic (for Apple the company: `fruit`, `recipe`). A video is dropped when one appears as a whole word in its caption or hashtags. Send none and the first run picks and saves some from your `intent`.
- `exclude_keywords_strict` (boolean, optional): Also check excludes against the spoken transcript.
- `meta_ads_enabled` (boolean, optional): Also collect matching Meta Ad Library ads, at no extra cost.
- `data_intelligence_enabled` (boolean, optional): AI breakdowns of the videos, $1.00 more per run. Covers only runs after you turn it on, and only the videos worth studying: off-topic, year-old and underperforming ones are skipped. Recurring agents can switch it later. [Details](https://dev.virlo.ai/docs/intelligence).
- `english_only` (boolean, optional): Default `true`. For another language, set `false` and write the `intent` and `keywords` in that language.
- `autopilot` (boolean, optional): Default `true`. After each run, a recurring agent adds and rewords keywords to keep finding new videos. It never removes yours and never adds charges. `false` keeps the setup exactly as you send it. It has no effect on one-time agents. [Details](https://dev.virlo.ai/docs/agents/autopilot).

> **Note:** Filter by views or date when you [read the videos](https://dev.virlo.ai/docs/agents#get-agent-videos), not here: `min_views`, `time_range`, or any unlisted field returns a free [`400` error](https://dev.virlo.ai/docs/errors).

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

**One-time request:**

```bash
# $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"
  }'
```

**Recurring request:**

```bash
# X-Cost shows 0.00, but each run costs $0.50 when it finishes:
# the first one now, then every week until you pause the agent.
curl -X POST https://api.virlo.ai/v1/agents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "is_recurring": true,
    "cadence": "weekly",
    "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 201 One-time:**

```json
{
  "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"
}
```

**Response 201 Recurring:**

```json
{
  "data": {
    "id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
    "name": "Protein Recipes",
    "is_recurring": true,
    "intent_keywords": null,
    "cadence": "0 0 * * 0",
    "next_run_at": "2026-09-27T02:14:08.000Z",
    "last_run_at": null,
    "job_id": "c29bfbf3-4fa6-470b-80d3-53364f916ea8",
    "latest_run": {
      "id": "5c8b1f20-3a4d-4e7f-9b12-6d2a1c7e8f90",
      "status": "pending"
    }
  },
  "message": "Agent created"
}
```

**Response 400 No cadence:**

```json
{
  "message": [
    "cadence must be \"daily\", \"weekly\", \"monthly\", or a valid cron expression that runs at most once per day",
    "cadence must be a string"
  ],
  "error": "Bad Request",
  "statusCode": 400,
  "code": "validation_error"
}
```

**Response 400 Long intent:**

```json
{
  "message": "intent must be at most 500 characters",
  "error": "Bad Request",
  "statusCode": 400,
  "code": "validation_error"
}
```

Save `data.id`: paste it over `{agent_id}` in every later call. Ignore `job_id`.

---

## Get agent

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id`

Settings, latest run, and live progress: the call you repeat while you wait. Free.

- `finalized` (boolean, optional): `true` once collecting and the AI report are done. Wait for this, not `latest_run.status`.
- `pending_jobs` (object[], optional): AI work still running. Wait each job's `retry_after_seconds` (currently 15) before checking again.
- `latest_run` (object, optional): Shaped like [Get run](https://dev.virlo.ai/docs/agents#get-agent-run). `status` goes `pending`, `processing`, then `completed`, `partial_failure` (one platform or keyword failed, results still usable), or `failed`.
- `intent_keywords` (string[], optional): The phrases actually searched, built from `intent` and `keywords`. Set soon after a run starts; editing either clears it until the next run.
- `last_run_at` (string, optional): When the last run fully wrapped up, creator outliers included. `null` on a new agent until then.
- `is_processing` (boolean, optional): Always `false` on one-time agents, even mid-run. Not a done signal.
- `autopilot` (boolean, optional): `true` when [autopilot](https://dev.virlo.ai/docs/agents/autopilot) is on, the default. `autonomy_level` and `autopilot_unlocked` are older fields; read this one instead.
- `pinned_keywords` (string[], optional): The keywords you set. Autopilot keeps all of them and only adds around them, so `keywords` can hold more. `null` for agents made in the Virlo app.

For a progress bar, use `stage`, `progress_pct`, and `eta_seconds` ([values](https://dev.virlo.ai/docs/async-data#progress)).

**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](https://dev.virlo.ai/docs/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.

**cURL request:**

```bash
curl https://api.virlo.ai/v1/agents/{agent_id} \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200 Done:**

```json
{
  "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"
    }
  }
}
```

**Response 200 Running:**

```json
{
  "data": {
    "id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
    "last_run_at": null,
    "pending_jobs": [
      {
        "type": "viral_analysis",
        "status": "processing",
        "poll_url": "/v1/agents/a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
        "result_path": "data",
        "webhook_event": "content_research_agent.run.completed",
        "retry_after_seconds": 15
      }
    ],
    "finalized": false,
    "progress_pct": 75,
    "stage": "analyzing",
    "eta_seconds": 893,
    "latest_run": {
      "id": "5c8b1f20-3a4d-4e7f-9b12-6d2a1c7e8f90",
      "status": "completed",
      "videos_linked": 283
    }
  }
}
```

---

## Get summary

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id/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.

- `counts` are the run's own tallies. For how many videos you can read, use `total` from [Get videos](https://dev.virlo.ai/docs/agents#get-agent-videos).
- `counts.sounds` is always `null` for now. `counts.creators` counts outlier creators only, and can read `0` until they're ready.
- `run.platform_counts` are counted before filters, so they can add up to more than `videos_linked`.
- Some YouTube creators show `username: "channel"`. Get their link from [creator outliers](https://dev.virlo.ai/docs/agents#get-agent-outliers).

**cURL request:**

```bash
curl https://api.virlo.ai/v1/agents/{agent_id}/summary \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "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

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id/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.

- `min_views` (integer, optional): Only videos with at least this many views.
- `platforms` (string[], optional): `youtube`, `tiktok`, `instagram`, as `tiktok,youtube` or repeated. Plural: `platform` returns `400`.
- `start_date / end_date` (string, optional): Publish date window, such as `2026-09-01` or a full timestamp.
- `order_by / sort` (string, optional): `publish_date` (default), `views`, or `created_at` (when Virlo added it). `desc` (default) or `asc`.
- `region` (string, optional): **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.
- `intent_match` (boolean, optional): Data Intelligence agents only. `true` keeps videos that fit your intent, but pages can then come back empty before the end, and `total` counts only that page. To find every match, page without it and check `intent_match.matches`.
- `include_transcript` (boolean, optional): `true` adds each video's `transcript`: the full text, plus timestamps where the transcript has them. Free. Transcripts make pages several times bigger, so use a smaller `limit`. See [Transcripts](https://dev.virlo.ai/docs/agents#agent-video-transcripts) below.
- `page / limit` (integer, optional): From 1, and 1 to 100 per page (default `50`, larger values cut to 100). `offset` or any unlisted parameter returns `400`.

### 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, or `null` when there are no timestamps.
- `source`: `transcribed` when Virlo turned the audio into text (always timed), or `platform` when it's the transcript TikTok or YouTube published (usually text only, with `segments` `null`). When both exist you get `transcribed`.

`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](https://dev.virlo.ai/docs/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.

> **Note:** **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](https://dev.virlo.ai/docs/agent-playbook#spotting-the-most-viral) does. [Creator outliers](https://dev.virlo.ai/docs/agents#get-agent-outliers) can sort by it directly.

**Full field reference**

- `publish_date` is UTC with no time zone suffix, such as `2026-09-23T14:55:48`.
- `duration` is the video's length in seconds, or `null` when the platform didn't report it. `sound.duration` is the length of the audio track, which can differ.
- YouTube usernames start with `@`; TikTok and Instagram ones don't. `author.country` is the creator's country, often `null`, not the video's upload region.
- `sound.cover_url`, `thumbnail_url`, and `author.avatar_url` are full links, or `null`. Some files are HEIC (`.heic`), which most browsers other than Safari can't show, so convert them before display.
- `intelligence_status` is `ready`, `pending`, `skipped`, or `disabled`. `skipped` means Virlo chose not to analyze the video, and `intelligence_skip_reason` says why: `intent_mismatch`, `too_old`, or `under_followers` ([details](https://dev.virlo.ai/docs/intelligence#when-fields-are-missing)). It is `null` on every other status. Agents with [Data Intelligence](https://dev.virlo.ai/docs/intelligence) off can still show `ready` on videos analyzed elsewhere, at no charge.
- `upload_region_source`: `tiktok_region`, `youtube_channel_country`, and `instagram_location_tag` are exact; `inferred_normalize` is a guess. About 3 in 10 videos have no region, and Instagram videos rarely do.

**cURL request:**

```bash
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 200:**

```json
{
  "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

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id/creators/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](https://dev.virlo.ai/docs/agents#get-agent-similar-creators) to find more like them.

- `order_by` (string, optional): `outlier_ratio` (default, average views per follower), `weighted_score`, `avg_views`, `follower_count`, or `rising` (growth since the previous run). With one run, or with `category`, `rising` falls back to Virality Score order (`ranking: "outlier_fallback"`).
- `sort` (string, optional): `desc` (default) or `asc`.
- `platform` (string, optional): `youtube`, `tiktok`, or `instagram`. Singular here: `platforms` returns `400`.
- `follower_tier` (string, optional): `nano` (under 10,000 followers), `micro` (10,000 to 100,000), `mid` (100,000 to 1 million), or `macro` (over 1 million).
- `category` (string, optional): Keep creators whose topics contain this text.
- `page / limit` (integer, optional): From 1, and 1 to 100 per page (default `50`).

> **Note:** 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.

**cURL request:**

```bash
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 200:**

```json
{
  "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

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id/trends/latest`

The trends the AI found in the latest run, ranked, with evidence videos and view and engagement totals: the themes from the [analysis](https://dev.virlo.ai/docs/agents#get-agent-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](https://dev.virlo.ai/docs/agents#get-agent-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](https://dev.virlo.ai/docs/agents#migrating-from-orbit-comet), here and in the analysis.

**cURL request:**

```bash
curl https://api.virlo.ai/v1/agents/{agent_id}/trends/latest \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "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

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id/analysis/latest`

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.

**cURL request:**

```bash
curl https://api.virlo.ai/v1/agents/{agent_id}/analysis/latest \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response 200:**

```json
{
  "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

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id/sounds`

The sounds in the agent's videos, ranked by how many of this agent's videos use each one. Free.

- `sort` (string, optional): Picks the ranking, not a direction: `video_count` (default), `usage_count` (uses across the whole platform, often `null`), or `rising` (alias `growth_7d`, the biggest growth since the previous run). Unknown values fall back to `video_count`.
- `page / limit` (integer, optional): 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](https://dev.virlo.ai/docs/sounds#usage-history), $0.05 per request.

> **Note:** Here `total` and `total_pages` are estimates. Page while `has_next_page` is `true`.

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/agents/{agent_id}/sounds \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d sort=rising \
  -d limit=20
```

**Response 200:**

```json
{
  "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

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id/hashtags`

Hashtag stats across the agent's videos: video count, views, engagement, growth between runs, and top creators. Free.

- `sort` (string, optional): Picks the ranking, not a direction: `volume` (default), `growth` (biggest jump since the last run), or `avg_views`. After one run, `growth` gives the same order as `volume`.
- `page / limit` (integer, optional): From 1, and 1 to 100 per page (default `50`).

`order_by` and `platform` return `400` here. `lifecycle` works as for [sounds](https://dev.virlo.ai/docs/agents#get-agent-sounds). `total` and `total_pages` are estimates, so page while `has_next_page` is `true`.

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/agents/{agent_id}/hashtags \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d sort=growth \
  -d limit=25
```

**Response 200:**

```json
{
  "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

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id/hooks`

The strongest opening lines (hooks) from the agent's videos, ranked by Virality Score. Parameters and fields: [Agent hooks](https://dev.virlo.ai/docs/hooks#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.

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/agents/{agent_id}/hooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d limit=20
```

---

## Get slideshows

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id/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](https://dev.virlo.ai/docs/agents#get-agent-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](https://dev.virlo.ai/docs/intelligence#slideshow-intelligence).

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/agents/{agent_id}/slideshows \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d region=US \
  -d limit=50
```

**Response 200:**

```json
{
  "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

**Endpoint:** `GET https://api.virlo.ai/v1/agents/:id/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.

- `order_by` (string, optional): `created_at` (default, when Virlo collected the ad) or `page_like_count` (the advertiser page's likes).
- `sort` (string, optional): `desc` (default) or `asc`.
- `page / limit` (integer, optional): From 1, and 1 to 100 per page (default `50`).

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/agents/{agent_id}/ads \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d limit=50
```

**Response 200:**

```json
{
  "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

**Endpoint:** `GET https://api.virlo.ai/v1/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`.

- `is_recurring` (boolean, optional): `true` for recurring agents only, `false` for one-time only.
- `include_inactive` (boolean, optional): Also list paused agents. Deleted agents never appear.
- `page / limit` (integer, optional): From 1, and 1 to 100 per page (default `50`).

There is no total. Page until a page has fewer than `limit` agents.

**cURL request:**

```bash
curl -G https://api.virlo.ai/v1/agents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d is_recurring=true \
  -d limit=50
```

**Response 200:**

```json
{
  "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

**Endpoint:** `PUT https://api.virlo.ai/v1/agents/:id`

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](https://dev.virlo.ai/docs/agents/autopilot#autonomy) never adds a billed run either. An agent can't switch type: sending `is_recurring` returns `400` (`property is_recurring should not exist`).

- `active` (boolean, optional): `false` pauses a recurring agent: no runs, no charges. `true` resumes it.
- `name / intent / platforms` (mixed, optional): Replace the current values, with the same rules as [Create agent](https://dev.virlo.ai/docs/agents#create-agent).
- `keywords` (string[], optional): Replaces the list, with the same rules as [Create agent](https://dev.virlo.ai/docs/agents#create-agent). On an agent made through the API, your new list becomes the [pinned](https://dev.virlo.ai/docs/agents/autopilot#pinned-keywords) set that autopilot always keeps.
- `cadence` (string, optional): Recurring agents only. On a one-time agent it returns `400` (`cadence can only be set on a recurring agent`).
- `exclude_keywords` (string[], optional): 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 your `intent`.
- `exclude_keywords_strict / meta_ads_enabled / english_only` (boolean, optional): Turn these on or off.
- `data_intelligence_enabled` (boolean, optional): Turning it on makes each later run $1.50.
- `autopilot` (boolean, optional): `true` turns [autopilot](https://dev.virlo.ai/docs/agents/autopilot) on. `false` turns it off and keeps the setup exactly as you set it.

**cURL request:**

```bash
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 200:**

```json
{
  "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"
}
```

**Response 400 Empty body:**

```json
{
  "message": "At least one field must be provided",
  "error": "Bad Request",
  "statusCode": 400,
  "code": "validation_error"
}
```

---

## Delete agent

**Endpoint:** `DELETE https://api.virlo.ai/v1/agents/:id`

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](https://dev.virlo.ai/docs/agents#update-agent). A deleted agent disappears from [List agents](https://dev.virlo.ai/docs/agents#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.

**cURL request:**

```bash
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**

- `follower_tier` (string, optional): The tiers from [creator outliers](https://dev.virlo.ai/docs/agents#get-agent-outliers), or `unknown` (no follower count, so `median_followers` and `median_posting_frequency_days` are `null`).
- `median_engagement_rate` (number, optional): A decimal (`0.071` means 7.1%). Compare creators only within a tier. A small `creator_count` makes it noisy.
- `median_videos_in_niche` (integer, optional): This agent's videos per creator. Often `1`.
- `median_posting_frequency_days` (number, optional): Days between videos in this agent's collection, counting only creators with at least two. Not their overall posting rate, and `null` when no creator in the tier has two.

**cURL:**

```bash
curl https://api.virlo.ai/v1/agents/{agent_id}/benchmarks \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**JSON:**

```json
{
  "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](https://dev.virlo.ai/docs/agents#get-agent-sounds) and [hashtags](https://dev.virlo.ai/docs/agents#get-agent-hashtags).

**Example**

**cURL:**

```bash
curl https://api.virlo.ai/v1/agents/{agent_id}/affinity \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**JSON:**

```json
{
  "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](https://dev.virlo.ai/docs/agents#get-agent-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**

- `limit` (integer, optional): 1 to 100. Default `20`. There is no paging: `page` returns `400`.

**cURL:**

```bash
curl -G https://api.virlo.ai/v1/agents/{agent_id}/creators/{creator_id}/similar \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d limit=20
```

**JSON:**

```json
{
  "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](https://dev.virlo.ai/docs/agents#get-agent-trends). Filter by `stable_key` to follow one trend across runs.

**Parameters and example**

- `stable_key` (string, optional): Show only one trend's history.
- `start_date / end_date` (string, optional): Only trends in this window.
- `page / limit` (integer, optional): From 1, and 1 to 100 per page (default `50`).

The `pagination` object's `total` is a real count.

**cURL:**

```bash
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](https://dev.virlo.ai/docs/agents#get-agent-analysis).

**Parameters and example**

- `start_date / end_date` (string, optional): Only analyses in this window.
- `page / limit` (integer, optional): From 1, and 1 to 100 per page (default `50`).

The `pagination` object's `total` is a real count.

**cURL:**

```bash
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**

- `page / limit` (integer, optional): From 1, and 1 to 100 per page (default `50`). Other parameters, such as `status`, 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](https://dev.virlo.ai/docs/agents#get-agent-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 your `intent`, by `english_only`, and by your excludes. A high `intent_filtered` means 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 than `videos_linked`.
- `total_videos_inserted` and `total_videos_updated`: videos new to Virlo, and videos Virlo already had, now refreshed.
- `duplicates_dropped` and `trends_detected` are currently always `0` and `null`.

**cURL:**

```bash
curl -G https://api.virlo.ai/v1/agents/{agent_id}/runs \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d limit=50
```

**JSON:**

```json
{
  "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](https://dev.virlo.ai/docs/agents#list-agent-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](https://dev.virlo.ai/docs/orbit) · [Comet](https://dev.virlo.ai/docs/comet).

In `insight_type`, `orbit` still means a one-time agent and `custom_niche` a recurring one.

---

More in Content Research Agents:

- [Autopilot](https://dev.virlo.ai/docs/agents/autopilot.md)
- [Intent cookbook](https://dev.virlo.ai/docs/intent-cookbook.md)
- [Data Intelligence](https://dev.virlo.ai/docs/intelligence.md)
