Content Research Agents

A Content Research Agent (CRA) is the unified, forward-facing way to run research on Virlo. One resource, /v1/agents, replaces the split Orbit (one-shot keyword search) and Comet (recurring niche monitor) endpoints — a single is_recurring flag decides the mode. One-shot agents cost $0.50 at creation; recurring agents are free to create and bill $0.50 per scheduled run (including the first); all reads are free.


Migrating from Orbit / Comet

Orbit and Comet remain available during a migration window and will be removed on August 3, 2026. Migrating is mostly a rename — the data shapes are the same.

Endpoint mapping

LegacyUnified
POST /v1/orbitPOST /v1/agents with is_recurring: false
POST /v1/cometPOST /v1/agents with is_recurring: true
GET /v1/orbit/:id · GET /v1/comet/:idGET /v1/agents/:id
GET /v1/{orbit,comet}/:id/videosGET /v1/agents/:id/videos
GET /v1/{orbit,comet}/:id/slideshowsGET /v1/agents/:id/slideshows
GET /v1/{orbit,comet}/:id/adsGET /v1/agents/:id/ads
GET /v1/{orbit,comet}/:id/creators/outliersGET /v1/agents/:id/creators/outliers
GET /v1/{orbit,comet}/:id/analysis[/latest]GET /v1/agents/:id/analysis[/latest]
GET /v1/{orbit,comet}/:id/trends[/latest]GET /v1/agents/:id/trends[/latest]
GET /v1/{orbit,comet}/:id/soundsGET /v1/agents/:id/sounds
GET /v1/comet/:id/hashtagsGET /v1/agents/:id/hashtags
GET /v1/comet/:id/benchmarksGET /v1/agents/:id/benchmarks
GET /v1/comet/:id/affinityGET /v1/agents/:id/affinity
GET /v1/comet/:id/creators/:cid/similarGET /v1/agents/:id/creators/:cid/similar
PUT /v1/comet/:idPUT /v1/agents/:id
DELETE /v1/comet/:idDELETE /v1/agents/:id

Every read is a same-path rename (/v1/orbit/:id/… or /v1/comet/:id/…/v1/agents/:id/…) and the IDs are identical, so you can migrate one call site at a time.

What changed

  • min_views / time_range / time_period are removed from creation (collection is system-managed). Move any view/date filtering to the videos endpoint query params.
  • intent is required on agents — it drives keyword quality, intent-match filtering, and the agent's self-optimization.
  • Webhook: subscribe to content_research_agent.run.completed (carries is_recurring) instead of orbit.run.completed / comet.run.completed. The legacy events keep firing until removal.
  • New: an autonomy surface (activity log + change proposals) — see below.

POST/v1/agents/suggest-keywords

Suggest keywords

Turns an intent into a quality-graded keyword set you can pass straight to Create agent. Free, synchronous, and creates nothing — call it as many times as you like until the quality score looks right, then spend the $0.50 on an agent that starts with good keywords.

Keyword quality is the single biggest driver of how useful an agent's results are, so this is the recommended first call for any new agent.

  • Name
    intent
    Type
    string
    Required
    *
    Description

    What the agent should research, in one sentence — the same string you'll pass to POST /v1/agents. Max 500 characters.

  • Name
    topic_hint
    Type
    string
    Description

    Short topic or agent-name hint to steer generation. Example: "Protein Recipes".

  • Name
    platforms
    Type
    string[]
    Description

    Platforms the agent will collect from — phrasing differs by platform, so passing this sharpens the output.

  • Name
    existing_keywords
    Type
    string[]
    Description

    The agent's current keywords. Pass these for refresh and opportunity modes so the engine avoids repeating what you already have.

  • Name
    mode
    Type
    string
    Description

    create (default) — build a fresh set from the intent. refresh — replace stale keywords on an existing agent. opportunity — find under-covered angles adjacent to the current set.

  • Name
    desired_count
    Type
    integer
    Description

    How many keywords to aim for. The engine clamps this to the data-backed 7–12 sweet spot: beyond ~15 keywords the off-target ratio climbs sharply, and single bare generic words cause 50–60% intent-filter loss.

  • Name
    use_web_grounding
    Type
    boolean
    Description

    Ground generation in current web results so timely phrasing is picked up. Slower — leave off for evergreen niches. Default false.

Reading the quality grade

quality.score is 0–100; quality.passes is true when the score clears the threshold and there are no critical issues. If it's false, sharpen your intent and call again — it costs nothing.

Each entry in quality.issues has a code, a severity (critical / warning / info), a human message, and often the offenders that triggered it. Codes: too_few_keywords, too_many_keywords, single_word_keywords, overly_long_keywords, duplicate_keywords, low_cluster_cohesion, empty_set.

quality.stats.core_token_coverage is the fraction (0–1) of keywords sharing the dominant core token in core_token. Low coverage means the set is semantically scattered — tight clusters around one core entity collect far less off-target content.

Request

POST
/v1/agents/suggest-keywords
curl -X POST https://api.virlo.ai/v1/agents/suggest-keywords \
  -H "Authorization: Bearer {token}" \
  -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": [
      "high protein recipe",
      "protein meal prep",
      "protein snack ideas",
      "high protein dessert",
      "protein overnight oats"
    ],
    "exclude_keywords": ["protein powder review", "supplement haul"],
    "reasoning": "Focused on preparation-style phrasing rather than product terms, which skew toward supplement marketing rather than recipe content.",
    "quality": {
      "score": 86,
      "passes": true,
      "issues": [
        {
          "code": "single_word_keywords",
          "severity": "warning",
          "message": "Single bare words collect a high share of off-topic content.",
          "offenders": ["protein"]
        }
      ],
      "stats": {
        "count": 5,
        "avg_words_per_keyword": 3.2,
        "single_word_count": 0,
        "long_keyword_count": 0,
        "duplicate_count": 0,
        "core_token_coverage": 1.0,
        "core_token": "protein"
      }
    },
    "timely_context_used": false
  }
}

POST/v1/agents

Create agent

Creates a one-shot (is_recurring: false) or recurring (is_recurring: true) agent and dispatches its first run immediately.

  • Name
    is_recurring
    Type
    boolean
    Required
    *
    Description

    false = one-shot (Orbit-equivalent). true = recurring monitor (Comet-equivalent).

  • Name
    intent
    Type
    string
    Required
    *
    Description

    What you want this agent to research, in one sentence. Drives keyword quality, intent_match filtering, and the agent's self-optimization. Example: "Track viral protein-recipe content for a fitness brand".

  • Name
    keywords
    Type
    string[]
    Required
    *
    Description

    1–50 keywords. 3–7 specific multi-word phrases covering synonyms of the same concept work best. Hashtag tokens are normalized (#melodichouse == melodic house).

  • Name
    platforms
    Type
    string[]
    Description

    youtube, tiktok, instagram. Defaults to all three. meta_ads is also accepted here, but prefer the meta_ads_enabled flag below — it's the supported way to turn ad collection on and keeps platforms meaning organic content.

  • Name
    cadence
    Type
    string
    Description

    Required when is_recurring: true. "daily", "weekly", "monthly", or a cron expression (must run at most once per day — sub-daily crons are rejected). Rejected for one-shot agents.

  • Name
    name
    Type
    string
    Description

    Human-friendly name. Defaults to a generated label.

  • Name
    exclude_keywords
    Type
    string[]
    Description

    Keywords to filter out of results.

  • Name
    exclude_keywords_strict
    Type
    boolean
    Description

    Also match exclude_keywords against transcripts. Default false.

  • Name
    meta_ads_enabled
    Type
    boolean
    Description

    Also collect Meta ads. Default false.

  • Name
    data_intelligence_enabled
    Type
    boolean
    Description

    Enable per-video AI intelligence (43 structured fields). Adds $1.00 to the base cost. Cannot be applied retroactively. Default false.

  • Name
    english_only
    Type
    boolean
    Description

    Restrict collection to English-language content. Default true. Set false to collect content in every language — the way to research non-English or global niches. Write your intent and keywords in the target language when you opt out: the keyword engine adapts to the language of your input. On a recurring agent this applies to future runs; it never re-filters already-collected content.

Request

POST
/v1/agents
curl -X POST https://api.virlo.ai/v1/agents \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "is_recurring": true,
    "intent": "Track viral protein-recipe content for a fitness brand",
    "keywords": ["high protein recipe", "protein meal prep", "protein snack ideas"],
    "platforms": ["youtube", "tiktok", "instagram"],
    "cadence": "weekly",
    "name": "Protein Recipes",
    "meta_ads_enabled": true
  }'

Response

{
  "data": {
    "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": [],
    "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",
    "autonomy_level": "suggest",
    "autopilot_unlocked": false,
    "cognition_enabled": true,
    "cadence": "weekly",
    "next_run_at": "2026-07-10T14:00:00Z",
    "last_run_at": null,
    "is_processing": true,
    "created_at": "2026-07-03T14:00:00Z",
    "updated_at": "2026-07-03T14:00:00Z",
    "latest_run": {
      "id": "5c8b1f20-3a4d-4e7f-9b12-6d2a1c7e8f90",
      "status": "processing"
    },
    "job_id": "job_7f2a9c1e4b6d"
  },
  "message": "Agent created"
}

GET/v1/agents

List agents

Lists your agents.

  • Name
    is_recurring
    Type
    boolean
    Description

    Filter to recurring (true) or one-shot (false) agents.

  • Name
    include_inactive
    Type
    boolean
    Description

    Include deactivated agents. Default false.

  • Name
    page
    Type
    integer
    Description

    1-indexed page. Default 1.

  • Name
    limit
    Type
    integer
    Description

    Results per page (max 100). Default 50.

Request

GET
/v1/agents
curl -G https://api.virlo.ai/v1/agents \
  -H "Authorization: Bearer {token}" \
  -d is_recurring=true \
  -d limit=50

Response

{
  "data": {
    "limit": 50,
    "page": 1,
    "count": 2,
    "agents": [
      {
        "id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
        "name": "Protein Recipes",
        "is_recurring": true,
        "active": true,
        "cadence": "weekly",
        "next_run_at": "2026-07-10T14:00:00Z",
        "last_run_at": "2026-07-03T14:18:00Z",
        "is_processing": false
      },
      {
        "id": "b2e4d9f3-1c8e-4b02-8d3f-3a6e7c9f2b55",
        "name": "Jeep Wrangler Mods",
        "is_recurring": false,
        "active": true,
        "cadence": null,
        "next_run_at": null,
        "last_run_at": "2026-07-02T09:31:00Z",
        "is_processing": false
      }
    ]
  }
}

GET/v1/agents/:id

Get agent

Returns the agent config, autonomy state, latest run, and async status. finalized: true means the latest run (scrape + AI analysis) is fully settled; while false, inspect pending_jobs[].

Alongside the config you sent, the response includes:

  • Name
    intent_keywords
    Type
    string[]
    Description

    The expanded keyword set the agent actually searched, derived from your intent + keywords. Null until the first run resolves it. Read this to see how the agent interpreted your intent — if the expansion drifted, sharpen intent rather than adding keywords.

  • Name
    latest_run
    Type
    object
    Description

    The most recent run, same shape as Get run.

  • Name
    pending_jobs
    Type
    object[]
    Description

    Secondary work still in flight, each with a type and retry_after_seconds to use as your poll interval. Empty when finalized is true.

  • Name
    autonomy_level / autopilot_unlocked / cognition_enabled
    Type
    Description

    Self-optimization state. See Autonomy.

Request

GET
/v1/agents/:id
curl https://api.virlo.ai/v1/agents/{agent_id} \
  -H "Authorization: Bearer {token}"

Response

{
  "data": {
    "id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
    "name": "Protein Recipes",
    "is_recurring": true,
    "active": true,
    "cadence": "weekly",
    "next_run_at": "2026-07-10T14:00:00Z",
    "last_run_at": "2026-07-03T14:18:00Z",
    "is_processing": false,
    "finalized": true,
    "latest_run": {
      "id": "5c8b1f20-3a4d-4e7f-9b12-6d2a1c7e8f90",
      "status": "completed",
      "videos_linked": 283,
      "outliers_identified": 12,
      "started_at": "2026-07-03T14:00:00Z",
      "completed_at": "2026-07-03T14:18:00Z"
    },
    "pending_jobs": [],
    "analysis": "Protein desserts and no-cook overnight recipes are driving the most outsized reach this week...",
    "analysis_data": { "key_highlight": "...", "themes": [] },
    "analysis_batch_start": "2026-06-26T14:00:00Z",
    "analysis_batch_end": "2026-07-03T14:00:00Z",
    "autonomy_level": "suggest",
    "autopilot_unlocked": false,
    "cognition_enabled": true
  }
}

PUT/v1/agents/:id

Update agent

Updates mutable config. Only fields you pass change. Collection scope (min_views/time_range) is system-managed and cannot be set here.

  • Name
    name
    Type
    string
    Description
    New name.
  • Name
    active
    Type
    boolean
    Description
    Activate or pause the agent.
  • Name
    keywords
    Type
    string[]
    Description
    Replace the keyword set.
  • Name
    platforms
    Type
    string[]
    Description
    Replace the platform set.
  • Name
    cadence
    Type
    string
    Description
    New cadence (recurring only).
  • Name
    exclude_keywords
    Type
    string[]
    Description
    Replace excludes.
  • Name
    exclude_keywords_strict
    Type
    boolean
    Description
    Toggle transcript-level exclusion.
  • Name
    meta_ads_enabled
    Type
    boolean
    Description
    Toggle Meta ads collection (future runs).
  • Name
    intent
    Type
    string
    Description
    Update the agent's intent.
  • Name
    data_intelligence_enabled
    Type
    boolean
    Description
    Toggle per-video intelligence for future runs.
  • Name
    english_only
    Type
    boolean
    Description
    Toggle English-only collection for future runs. true = English only, false = all languages. Does not re-filter content already collected.

Request

PUT
/v1/agents/:id
curl -X PUT https://api.virlo.ai/v1/agents/{agent_id} \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{ "keywords": ["high protein recipe", "protein meal prep", "protein desserts"] }'

DELETE/v1/agents/:id

Delete agent

Soft-deletes the agent (stops future runs; previously collected data remains accessible until purged). Returns 204 No Content.

Request

DELETE
/v1/agents/:id
curl -X DELETE https://api.virlo.ai/v1/agents/{agent_id} \
  -H "Authorization: Bearer {token}"

GET/v1/agents/:id/videos

Get videos

Videos collected by the agent. This is where you filter the broad collection — for free, any way you like.

  • Name
    min_views
    Type
    integer
    Description

    Minimum view count (read-time filter). Example: 100000.

  • Name
    platforms
    Type
    string[]
    Description

    Filter by platform(s).

  • Name
    start_date
    Type
    string
    Description

    Only videos published on/after this ISO date. Example: 2026-01-01T00:00:00Z.

  • Name
    end_date
    Type
    string
    Description

    Only videos published on/before this ISO date.

  • Name
    order_by
    Type
    string
    Description

    views | publish_date | created_at.

  • Name
    sort
    Type
    string
    Description

    asc | desc.

  • Name
    intent_match
    Type
    boolean
    Description

    When the agent used data_intelligence_enabled, return only intent-matching (true) or non-matching (false) videos.

  • Name
    region
    Type
    string
    Description

    Beta. Filter by resolved upload region — an ISO-3166-1 alpha-2 country code (case-insensitive), e.g. US, CA, GB, RU, AU, JP, BR. Only videos whose region resolved to that country are returned; videos without a resolved region are excluded. See the Upload region note below.

  • Name
    page
    Type
    integer
    Description
    1-indexed page. Default 1.
  • Name
    limit
    Type
    integer
    Description
    Results per page (max 100). Default 50.

Request

GET
/v1/agents/:id/videos
curl -G https://api.virlo.ai/v1/agents/{agent_id}/videos \
  -H "Authorization: Bearer {token}" \
  -d min_views=100000 \
  -d start_date=2026-01-01T00:00:00Z \
  -d order_by=views \
  -d sort=desc \
  -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-06-28T18:22:00Z",
        "author": {
          "username": "fitcoachjen",
          "avatar_url": "https://.../avatar.jpg",
          "followers": 48200,
          "country": "US",
          "verified": false
        },
        "hashtags": ["proteinrecipe", "highprotein", "healthydessert"],
        "thumbnail_url": "https://.../thumb.jpg",
        "keyword_found_by": "high protein recipe",
        "upload_region": "US",
        "upload_region_source": "tiktok_region",
        "intent_match": { "matches": true, "reasoning": "Recipe tutorial matches the stated intent." },
        "intelligence": { "hook_type": "problem_solution", "content_format": "tutorial" },
        "intelligence_status": "ready",
        "is_duet": false,
        "is_stitch": false,
        "sound": { "id": "8f6e5c50-1451-4007-a120-92744e632dad", "title": "original sound - fitcoachjen" }
      }
    ]
  }
}

GET/v1/agents/:id/slideshows

Get slideshows

Slideshows (image carousels) collected by the agent. Same read-time filters as videos, including region.

Each slideshow carries a region field (ISO-3166-1 alpha-2). Slideshow region comes straight from TikTok, so it's the highest-coverage region signal Virlo exposes — a slideshow-heavy agent is the best surface for reliable regional filtering.

  • Name
    region
    Type
    string
    Description

    Beta. Filter by upload region — an ISO-3166-1 alpha-2 country code (case-insensitive), e.g. TH, US, BR. Only slideshows uploaded from that country are returned.

  • Name
    min_views
    Type
    integer
    Description
    Minimum view count (read-time filter).
  • Name
    start_date
    Type
    string
    Description
    Only slideshows published on/after this ISO date.
  • Name
    end_date
    Type
    string
    Description
    Only slideshows published on/before this ISO date.
  • Name
    order_by
    Type
    string
    Description
    views | publish_date | created_at.
  • Name
    sort
    Type
    string
    Description
    asc | desc.
  • Name
    page
    Type
    integer
    Description
    1-indexed page. Default 1.
  • Name
    limit
    Type
    integer
    Description
    Results per page (max 100). Default 50.

Request

GET
/v1/agents/:id/slideshows
curl -G https://api.virlo.ai/v1/agents/{agent_id}/slideshows \
  -H "Authorization: Bearer {token}" \
  -d limit=50

GET/v1/agents/:id/ads

Get ads

Meta ads collected when meta_ads_enabled was set. Supports order_by (created_at | page_like_count), sort, page, limit.

Request

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

GET/v1/agents/:id/creators/outliers

Get creator outliers

Creators who significantly outperform their follower count within the agent's corpus. Prefer order_by=weighted_score. Also supports platform, page, limit, sort, plus follower_tier (nano / micro / mid / macro) and category (topic substring) filters.

order_by=rising ranks by true run-over-run velocity (follower/view growth) and adds growth_followers / growth_views / growth_video_count fields per row; on a young agent without two snapshots it transparently falls back to the weighted-outlier ranking (the response echoes ranking: "outlier_fallback"). Pass a row's author_id to similar creators to explore adjacent creators.

Each row carries more than the ranking fields — enough to qualify a creator without a second call:

  • Name
    weighted_score
    Type
    number
    Description

    The ranking signal: ln(views/followers) × ln(followers). ≥ 35 exceptional, 25–35 very strong, 18–25 strong, 10–18 promising.

  • Name
    outlier_ratio
    Type
    number
    Description

    Average views divided by follower count.

  • Name
    content_angle
    Type
    string
    Description

    AI-derived description of what this creator's angle actually is — the fastest way to judge fit without watching their videos.

  • Name
    creator_topics
    Type
    string[]
    Description

    Topics this creator covers across their catalogue.

  • Name
    matching_topics
    Type
    string[]
    Description

    The subset of creator_topics that overlaps your agent's intent. A creator with many topics but one match is a tangential hit; several matches means they live in your niche.

  • Name
    median_views
    Type
    integer
    Description

    Median views — more robust than avg_views when one video went viral.

  • Name
    breakout_video_count
    Type
    integer
    Description

    How many of their videos in this corpus were outliers, not just one lucky hit.

  • Name
    posts_per_week
    Type
    number
    Description

    Posting cadence, derived from the publish-date spread.

  • Name
    avg_engagement_rate
    Type
    number
    Description

    Mean engagement rate across analyzed videos.

  • Name
    videos
    Type
    object[]
    Description

    The specific videos that qualified this creator as an outlier.

  • Name
    platform
    Type
    string
    Description

    youtube | tiktok | instagram.

  • Name
    identified_at
    Type
    string
    Description

    When this creator was first flagged as an outlier.

Request

GET
/v1/agents/:id/creators/outliers
curl -G https://api.virlo.ai/v1/agents/{agent_id}/creators/outliers \
  -H "Authorization: Bearer {token}" \
  -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": true,
    "outliers": [
      {
        "author_id": "c3d4e5f6-a7b8-4901-9c2d-3e4f5a6b7c8d",
        "platform": "tiktok",
        "creator_url": "https://www.tiktok.com/@fitcoachjen",
        "creator_avatar_url": "https://auth.virlo.ai/storage/v1/object/public/avatars/….jpg",
        "follower_count": 48200,
        "avg_views": 512000,
        "median_views": 431000,
        "outlier_ratio": 10.6,
        "weighted_score": 25.4,
        "videos_analyzed": 14,
        "breakout_video_count": 4,
        "posts_per_week": 5.2,
        "avg_engagement_rate": 0.081,
        "content_angle": "Three-ingredient high-protein desserts filmed in one unbroken take.",
        "creator_topics": ["fitness", "recipes", "meal prep"],
        "matching_topics": ["recipes", "meal prep"],
        "identified_at": "2026-07-03T14:16:00Z"
      }
    ]
  }
}

GET/v1/agents/:id/sounds

Get sounds

Top sounds across the agent's collected videos, ranked by usage. Free. Use sort=rising (or growth_7d) to rank by run-over-run momentum — rows carry growth_video_count, growth_views, and a new / rising / steady / fading lifecycle label (growth fields are null until the agent has two run snapshots). Pivot to GET /v1/sounds/:sound_id/usage-history for a sound's full time-series.

Request

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

Response

{
  "data": [
    {
      "id": "8f6e5c50-1451-4007-a120-92744e632dad",
      "title": "Saxophones getting louder",
      "platform": "tiktok",
      "usage_count": 138106,
      "video_count": 42,
      "avg_views": 612000,
      "growth_video_count": 18,
      "growth_views": 240000,
      "lifecycle": "rising"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 63,
    "total_pages": 4,
    "has_next_page": true,
    "has_prev_page": false
  }
}

GET/v1/agents/:id/hashtags

Get hashtags

Per-hashtag analytics computed over the agent's collected videos: volume, total/average views, average engagement, run-over-run growth with a new / rising / steady / fading lifecycle label, and the most active creators per hashtag. Free. Sort with sort=volume (default), growth, or avg_views.

Request

GET
/v1/agents/:id/hashtags
curl -G https://api.virlo.ai/v1/agents/{agent_id}/hashtags \
  -H "Authorization: Bearer {token}" \
  -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://.../avatar.jpg",
          "video_count": 8
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 25,
    "total": 118,
    "total_pages": 5,
    "has_next_page": true,
    "has_prev_page": false
  }
}

GET/v1/agents/:id/benchmarks

Get benchmarks

Median engagement rate, follower count, niche video count, and posting frequency across the agent's creators, bucketed by follower tier. Free. Answers "how does this creator compare to the genre norm?".

Returns one row per tier — data is a plain array with no pagination.

  • Name
    follower_tier
    Type
    string
    Description

    nano | micro | mid | macro.

  • Name
    creator_count
    Type
    integer
    Description

    Creators in this tier within the agent's corpus. Treat a tier with a small creator_count as directional — a median over a handful of creators is noisy.

  • Name
    median_followers
    Type
    integer
    Description

    Median follower count for the tier.

  • Name
    median_engagement_rate
    Type
    number
    Description

    Median engagement rate (0–1). Compare a candidate creator against their own tier — engagement falls predictably as follower count rises, so cross-tier comparison is misleading.

  • Name
    median_videos_in_niche
    Type
    integer
    Description

    Median number of the agent's collected videos per creator in the tier — a proxy for how committed the tier is to this niche versus passing through it.

  • Name
    median_posting_frequency_days
    Type
    number
    Description

    Median gap in days between posts.

Request

GET
/v1/agents/:id/benchmarks
curl https://api.virlo.ai/v1/agents/{agent_id}/benchmarks \
  -H "Authorization: Bearer {token}"

Response

{
  "data": [
    {
      "follower_tier": "micro",
      "creator_count": 48,
      "median_followers": 42000,
      "median_engagement_rate": 0.071,
      "median_videos_in_niche": 6,
      "median_posting_frequency_days": 1.4
    }
  ]
}

GET/v1/agents/:id/affinity

Get affinity

Beta — directional signal; methodology may evolve. Genre-adjacency derived from the agent's corpus: dominant creator topics, co-occurring hashtags, and co-occurring sounds. Not a follow-graph. Free.

Request

GET
/v1/agents/:id/affinity
curl https://api.virlo.ai/v1/agents/{agent_id}/affinity \
  -H "Authorization: Bearer {token}"

GET/v1/agents/:id/creators/:creator_id/similar

Get similar creators

Beta — directional signal; methodology may evolve. Other creators in the agent's corpus ranked by shared hashtags and shared sounds with the target creator (co-occurrence; use the author_id from creator outliers). Free. Supports limit (default 20, max 100).

Request

GET
/v1/agents/:id/creators/:creator_id/similar
curl -G https://api.virlo.ai/v1/agents/{agent_id}/creators/{author_id}/similar \
  -H "Authorization: Bearer {token}" \
  -d limit=20

GET/v1/agents/:id/analysis/latest

Get latest analysis

The full structured AI analysis from the most recent completed cycle — analysis is a short human-readable highlight, analysis_data carries the full object (themes, connecting_thread, viral_tactics, timing_analysis, top_10_breakdown, …). Free.

The latest analysis fields are also merged into GET /v1/agents/:id (analysis, analysis_data, analysis_batch_start, analysis_batch_end). For every past batch, see analysis history.

Request

GET
/v1/agents/:id/analysis/latest
curl https://api.virlo.ai/v1/agents/{agent_id}/analysis/latest \
  -H "Authorization: Bearer {token}"

Response

{
  "data": {
    "id": "d5e6f7a8-b9c0-4d12-8e34-5f6a7b8c9d01",
    "insight_type": "content_research_agent",
    "reference_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
    "batch_start": "2026-06-26T14:00:00Z",
    "batch_end": "2026-07-03T14:00:00Z",
    "analysis": "Protein desserts and no-cook overnight recipes are driving the most outsized reach this week...",
    "analysis_data": {
      "key_highlight": "No-cook 'overnight' protein recipes are the breakout format.",
      "overview": "...",
      "themes": [
        {
          "why_it_works": "Zero-effort framing lowers the barrier to trying the recipe.",
          "tactics": ["show the jar in the first frame", "call out the protein grams in text"],
          "confidence": 0.82,
          "evidence_video_ids": ["e7c2a1b4-9f3d-4e88-8a12-5b6c7d8e9f01"]
        }
      ],
      "top_10_breakdown": []
    },
    "video_count": 283,
    "analyzed_video_ids": ["e7c2a1b4-9f3d-4e88-8a12-5b6c7d8e9f01"],
    "model_used": "claude-sonnet",
    "created_at": "2026-07-03T14:17:00Z"
  }
}

GET/v1/agents/:id/analysis

Get analysis history

Paginated list of every completed analysis batch for this agent, newest first. Same item shape as latest analysis. Free.

  • Name
    page
    Type
    integer
    Description

    1-indexed page. Default 1.

  • Name
    limit
    Type
    integer
    Description

    Results per page, 1–100. Default 50.

  • Name
    start_date
    Type
    string
    Description

    Optional window start (YYYY-MM-DD).

  • Name
    end_date
    Type
    string
    Description

    Optional window end (YYYY-MM-DD).

Request

GET
/v1/agents/:id/analysis
curl -G https://api.virlo.ai/v1/agents/{agent_id}/analysis \
  -H "Authorization: Bearer {token}" \
  -d limit=20

GET/v1/agents/:id/trends/latest

Every trend detected in the most recent completed analysis cycle. Each trend carries evidence videos, aggregate engagement stats, a stable_key for time-series joins, and a new / rising / steady / fading status versus the previous cycle. Free.

For every past trend row (or one trend's trajectory), see trends history.

Request

GET
/v1/agents/:id/trends/latest
curl https://api.virlo.ai/v1/agents/{agent_id}/trends/latest \
  -H "Authorization: Bearer {token}"

Response

{
  "data": {
    "reference_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
    "insight_type": "content_research_agent",
    "viral_insight_id": "f1a2b3c4-d5e6-4f78-9a01-2b3c4d5e6f70",
    "batch_start": "2026-06-26T14:00:00Z",
    "batch_end": "2026-07-03T14:00:00Z",
    "total": 6,
    "trends": [
      {
        "id": "aa11bb22-cc33-4d44-8e55-6f778899aa00",
        "rank": 1,
        "stable_key": "overnight-protein-oats",
        "name": "No-cook overnight protein oats",
        "why_it_works": "Zero-effort framing + high protein payoff in one scroll.",
        "tactics": ["show the jar first", "text-overlay the protein grams"],
        "confidence": 0.86,
        "evidence_video_ids": ["e7c2a1b4-9f3d-4e88-8a12-5b6c7d8e9f01"],
        "video_count": 38,
        "total_views": 18400000,
        "total_likes": 2100000,
        "total_comments": 54000,
        "total_shares": 210000,
        "avg_virality_score": 22.7,
        "platform_breakdown": { "tiktok": 24, "instagram": 9, "youtube": 5 },
        "top_creators": ["fitcoachjen"],
        "peak_hour_utc": 18,
        "status": "rising",
        "first_seen_at": "2026-06-19T14:00:00Z",
        "prev_video_count": 21,
        "prev_total_views": 9200000,
        "created_at": "2026-07-03T14:17:00Z"
      }
    ]
  }
}

GET/v1/agents/:id/trends

Paginated list of every trend row produced for this agent, newest first. Filter by stable_key to follow one trend across runs — a trend moving new → rising with growing video_count is the strongest "post about this now" signal on recurring agents. Free.

  • Name
    page
    Type
    integer
    Description

    1-indexed page. Default 1.

  • Name
    limit
    Type
    integer
    Description

    Results per page, 1–100. Default 50.

  • Name
    stable_key
    Type
    string
    Description

    Optional. Isolate a single recurring trend's trajectory.

  • Name
    start_date
    Type
    string
    Description

    Optional window start (YYYY-MM-DD).

  • Name
    end_date
    Type
    string
    Description

    Optional window end (YYYY-MM-DD).

Request

GET
/v1/agents/:id/trends
curl -G https://api.virlo.ai/v1/agents/{agent_id}/trends \
  -H "Authorization: Bearer {token}" \
  -d stable_key=overnight-protein-oats \
  -d limit=50

GET/v1/agents/:id/runs

List runs

Lists the agent's runs (one for a one-shot agent, one per cycle for a recurring agent), newest first. Free. Fetch a single run with Get run.

Each run is a collection report — it tells you not just how many videos landed, but how many were discarded and why. When an agent returns less than you expected, this is the endpoint that explains it.

  • Name
    status
    Type
    string
    Description

    pending | processing | completed | failed | partial_failure. partial_failure is a usable terminal state — one platform or keyword failed, the rest succeeded.

  • Name
    videos_linked
    Type
    integer
    Description

    Videos attached to the agent by this run. slideshows_linked and meta_ads_linked are the equivalents for those content types.

  • Name
    youtube_count / tiktok_count / instagram_count
    Type
    integer
    Description

    Per-platform contribution for this run — the quickest way to spot one platform underdelivering.

  • Name
    intent_filtered
    Type
    integer
    Description

    Videos collected but dropped for not matching your intent. A high number relative to videos_linked means your keywords are pulling in off-topic content — tighten them or sharpen the intent.

  • Name
    language_filtered_count
    Type
    integer
    Description

    Videos dropped by the english_only filter. If this is large, the niche is mostly non-English — set english_only: false.

  • Name
    exclude_keywords_filtered
    Type
    integer
    Description

    Videos dropped by your exclude_keywords.

  • Name
    duplicates_dropped
    Type
    integer
    Description

    Videos already collected by an earlier run of this agent.

  • Name
    keyword_breakdown
    Type
    object[]
    Description

    Per-keyword collection detail: keyword, videos_inserted, videos_updated, and per-platform counts. The single most useful field for tuning an agent — it shows exactly which keywords are producing and which are dead weight.

  • Name
    outliers_identified
    Type
    integer
    Description

    Creator outliers detected in this run's corpus. See creator outliers.

  • Name
    trends_detected
    Type
    string[]
    Description

    Trend names detected in this cycle. Full rows via trends.

  • Name
    execution_time_ms
    Type
    integer
    Description

    Wall-clock duration of the run.

Request

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

Response

{
  "data": {
    "agent_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
    "limit": 50,
    "offset": 0,
    "count": 2,
    "runs": [
      {
        "id": "5c8b1f20-3a4d-4e7f-9b12-6d2a1c7e8f90",
        "agent_id": "a1f3c8e2-0b7d-4a91-9c2e-2f5d6b8e1a44",
        "status": "completed",
        "videos_linked": 283,
        "slideshows_linked": 41,
        "meta_ads_linked": 0,
        "youtube_count": 62,
        "tiktok_count": 178,
        "instagram_count": 43,
        "total_videos_inserted": 283,
        "total_videos_updated": 61,
        "total_videos_failed": 0,
        "intent_filtered": 172,
        "language_filtered_count": 0,
        "exclude_keywords_filtered": 14,
        "duplicates_dropped": 0,
        "outliers_identified": 12,
        "trends_detected": ["No-cook overnight protein oats"],
        "keyword_breakdown": [
          {
            "keyword": "high protein recipe",
            "videos_inserted": 41,
            "videos_updated": 12,
            "tiktok_count": 27,
            "youtube_count": 12,
            "instagram_count": 2
          }
        ],
        "execution_time_ms": 1080000,
        "created_at": "2026-07-03T14:00:00Z",
        "started_at": "2026-07-03T14:00:00Z",
        "completed_at": "2026-07-03T14:18:00Z"
      }
    ]
  }
}

GET/v1/agents/:id/runs/:run_id

Get run

Fetch one run by UUID. Returns the same object as List runs — status, collection counts, filter counts, keyword_breakdown, and timing. 404 if the run is not on this agent. Free.

Request

GET
/v1/agents/:id/runs/:run_id
curl https://api.virlo.ai/v1/agents/{agent_id}/runs/{run_id} \
  -H "Authorization: Bearer {token}"

Autonomy overview

Agents don't just collect — they reflect on their own performance and propose safe changes to keep finding content without manual babysitting. When an agent's yield drops, it can propose refreshing stale keywords or widening a starved collection window (and, as a last resort, dropping the view floor).

Self-optimization applies to recurring agents only — a one-shot runs once, so there's no future run to improve. One-shot agents won't produce proposals or activity.

How it works

  • Every proposed change is surfaced as a proposal with a human-readable rationale and a before/after diff.
  • autonomy_level: "suggest" (default) — proposals wait for your approval via apply/dismiss.
  • autonomy_level: "autopilot" — safe changes auto-apply. Autopilot must be unlocked first by approving one proposal manually, and it only ever widens collection — it never restricts what you see.
  • cognition_enabled: false pauses self-optimization entirely (the agent keeps collecting).
  • Everything the agent decides is recorded in its activity log, and any applied change is revertible.

GET/v1/agents/:id/activity

Get activity

The agent's decision log — reflections, milestones, and the changes it has made over time. Free. Supports limit (max 100).

Request

GET
/v1/agents/:id/activity
curl -G https://api.virlo.ai/v1/agents/{agent_id}/activity \
  -H "Authorization: Bearer {token}" \
  -d limit=20

GET/v1/agents/:id/proposals

List proposals

Self-optimization proposals. Filter with status (pending | applied | auto_applied | dismissed | reverted). Each proposal includes its type (keyword_refresh | filter_change), a rationale, and a diff.

Request

GET
/v1/agents/:id/proposals
curl -G https://api.virlo.ai/v1/agents/{agent_id}/proposals \
  -H "Authorization: Bearer {token}" \
  -d status=pending

POST

Apply / dismiss / revert a proposal

  • POST /v1/agents/:id/proposals/:proposal_id/apply — approve and apply a pending proposal. The first manual apply unlocks autopilot for the agent.
  • POST /v1/agents/:id/proposals/:proposal_id/dismiss — reject a pending proposal.
  • POST /v1/agents/:id/proposals/:proposal_id/revert — roll an applied change back to the prior config.

Request

POST
/v1/agents/:id/proposals/:proposal_id/apply
curl -X POST https://api.virlo.ai/v1/agents/{agent_id}/proposals/{proposal_id}/apply \
  -H "Authorization: Bearer {token}"

PUT/v1/agents/:id/autonomy

Set autonomy

Choose how much the agent may change on its own.

  • Name
    autonomy_level
    Type
    string
    Description

    suggest (changes wait for approval) or autopilot (safe widenings auto-apply; must be unlocked first).

  • Name
    cognition_enabled
    Type
    boolean
    Description

    Master switch for self-optimization. false pauses all reflection + proposals.

Request

PUT
/v1/agents/:id/autonomy
curl -X PUT https://api.virlo.ai/v1/agents/{agent_id}/autonomy \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{ "autonomy_level": "autopilot" }'

Was this page helpful?