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. Creating an agent costs $0.50 (recurring agents bill per scheduled run); all reads are free.
Collection scope is system-managed. You no longer choose a min_views floor or a time_range window at creation — agents collect the widest relevant net (cadence-aware) so nothing is missed. You filter at read time on the videos endpoint (min_views, start_date, end_date, platforms, order_by), for free, as many ways as you like without re-running the job.
All /v1/agents endpoints use snake_case for parameters and response fields, and wrap responses in a { "data": { ... } } envelope. Poll the agent resource and rely on finalized: true (not wall-clock time) to know a run is fully settled.
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
| Legacy | Unified |
|---|---|
POST /v1/orbit | POST /v1/agents with is_recurring: false |
POST /v1/comet | POST /v1/agents with is_recurring: true |
GET /v1/orbit/:id · GET /v1/comet/:id | GET /v1/agents/:id |
GET /v1/{orbit,comet}/:id/videos | GET /v1/agents/:id/videos |
GET /v1/{orbit,comet}/:id/slideshows | GET /v1/agents/:id/slideshows |
GET /v1/{orbit,comet}/:id/ads | GET /v1/agents/:id/ads |
GET /v1/{orbit,comet}/:id/creators/outliers | GET /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/sounds | GET /v1/agents/:id/sounds |
GET /v1/comet/:id/hashtags | GET /v1/agents/:id/hashtags |
GET /v1/comet/:id/benchmarks | GET /v1/agents/:id/benchmarks |
GET /v1/comet/:id/affinity | GET /v1/agents/:id/affinity |
GET /v1/comet/:id/creators/:cid/similar | GET /v1/agents/:id/creators/:cid/similar |
PUT /v1/comet/:id | PUT /v1/agents/:id |
DELETE /v1/comet/:id | DELETE /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_periodare removed from creation (collection is system-managed). Move any view/date filtering to thevideosendpoint query params.intentis required on agents — it drives keyword quality, intent-match filtering, and the agent's self-optimization.- Webhook: subscribe to
content_research_agent.run.completed(carriesis_recurring) instead oforbit.run.completed/comet.run.completed. The legacy events keep firing until removal. - New: an autonomy surface (activity log + change proposals) — see below.
During migration the legacy endpoints still accept min_views/time_range/time_period, but they are ignored (accepted for backward compatibility only). Filtering must move to read time.
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_matchfiltering, 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.
- 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_keywordsagainst transcripts. Defaultfalse.
- 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 (40+ fields). Adds $1.00 to the base cost. Cannot be applied retroactively. Default
false.
There is no min_views or time_range here — collection scope is system-managed. Filter at read time on GET /v1/agents/:id/videos. If you send them anyway they're accepted for backward compatibility, echoed back with system defaults, and otherwise ignored.
Cadence shortcuts ("daily" / "weekly" / "monthly") work exactly like a cron expression — both are valid. A raw cron must run at most once per day; sub-daily crons are rejected.
Team-level autopilot unlock. New agents default to autonomy_level: "suggest" and autopilot_unlocked: false. But once anyone on your team has unlocked autopilot (by manually applying one proposal), newly created agents come back with autonomy_level: "autopilot" and autopilot_unlocked: true by default — this is expected, not a bug. See Autonomy.
Request
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,
"is_deleted": false,
"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,
"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",
"finalized": false
},
"job_id": "job_7f2a9c1e4b6d"
},
"message": "Agent created"
}
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
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 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[].
Request
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
}
}
While a run is still settling, finalized is false and pending_jobs[] lists what's in flight (each with a type and retry_after_seconds you can use as your poll interval). status: "completed" on latest_run alone is not the done signal — wait for finalized: true. partial_failure is a usable terminal state.
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.
Request
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 agent
Soft-deletes the agent (stops future runs; previously collected data remains accessible until purged). Returns 204 No Content.
Request
curl -X DELETE https://api.virlo.ai/v1/agents/{agent_id} \
-H "Authorization: Bearer {token}"
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
page- Type
- integer
- Description
- 1-indexed page. Default
1.
- Name
limit- Type
- integer
- Description
- Results per page (max 100). Default
50.
Request
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": {
"id": "c3d4e5f6-a7b8-4901-9c2d-3e4f5a6b7c8d",
"username": "fitcoachjen",
"avatar_url": "https://.../avatar.jpg",
"url": "https://www.tiktok.com/@fitcoachjen",
"verified": false
},
"hashtags": ["proteinrecipe", "highprotein", "healthydessert"],
"thumbnail_url": "https://.../thumb.jpg",
"keyword_found_by": "high protein recipe",
"intent_match": true,
"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" }
}
]
}
}
Pagination for videos / slideshows / ads / outliers uses total + limit + offset nested inside data (alongside the result array). Sounds and hashtags use a different top-level pagination envelope — see those endpoints.
Get slideshows
Slideshows (image carousels) collected by the agent. Same read-time filters as videos.
Request
curl -G https://api.virlo.ai/v1/agents/{agent_id}/slideshows \
-H "Authorization: Bearer {token}" \
-d limit=50
Get ads
Meta ads collected when meta_ads_enabled was set. Supports order_by (created_at | page_like_count), sort, page, limit.
Request
curl -G https://api.virlo.ai/v1/agents/{agent_id}/ads \
-H "Authorization: Bearer {token}" \
-d limit=50
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. Pass a row's author_id to similar creators to explore adjacent creators.
Request
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,
"hasMore": true,
"outliers": [
{
"id": "c3d4e5f6-a7b8-4901-9c2d-3e4f5a6b7c8d",
"author": {
"id": "c3d4e5f6-a7b8-4901-9c2d-3e4f5a6b7c8d",
"username": "fitcoachjen",
"avatar_url": "https://.../avatar.jpg",
"url": "https://www.tiktok.com/@fitcoachjen",
"verified": false
},
"follower_count": 48200,
"avg_views": 512000,
"outlier_ratio": 10.6,
"weighted_score": 25.4,
"videos_analyzed": 14
}
]
}
}
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
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 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
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", "video_count": 8, "avg_views": 512000 }
]
}
],
"pagination": {
"page": 1,
"limit": 25,
"total": 118,
"total_pages": 5,
"has_next_page": true,
"has_prev_page": false
}
}
Get benchmarks
Median engagement rate, follower count, niche video count, and posting frequency across the agent's creators, bucketed by follower tier (nano / micro / mid / macro). Free. Useful for "how does a creator compare to the genre norm?".
Request
curl https://api.virlo.ai/v1/agents/{agent_id}/benchmarks \
-H "Authorization: Bearer {token}"
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
curl https://api.virlo.ai/v1/agents/{agent_id}/affinity \
-H "Authorization: Bearer {token}"
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
curl -G https://api.virlo.ai/v1/agents/{agent_id}/creators/{author_id}/similar \
-H "Authorization: Bearer {token}" \
-d limit=20
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 the full history, use GET /v1/agents/:id/analysis with page / limit / start_date / end_date.
Request
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 latest trends
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 the full history, use GET /v1/agents/:id/trends with page / limit / stable_key / start_date / end_date — filtering by stable_key gives you one trend's trajectory over time.
Request
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"
}
]
}
}
List runs
Lists the agent's runs (one for a one-shot agent, one per cycle for a recurring agent). Fetch a single run with GET /v1/agents/:id/runs/:run_id.
Request
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",
"agent_name": "Protein Recipes",
"total": 3,
"limit": 50,
"offset": 0,
"runs": [
{
"id": "5c8b1f20-3a4d-4e7f-9b12-6d2a1c7e8f90",
"status": "completed",
"finalized": true,
"videos_linked": 283,
"outliers_identified": 12,
"started_at": "2026-07-03T14:00:00Z",
"completed_at": "2026-07-03T14:18:00Z"
},
{
"id": "4b7a0e19-2c3d-4d6e-8a01-5c1b0d6e7f89",
"status": "partial_failure",
"finalized": true,
"videos_linked": 194,
"outliers_identified": 8,
"started_at": "2026-06-26T14:00:00Z",
"completed_at": "2026-06-26T14:21:00Z"
}
]
}
}
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: falsepauses self-optimization entirely (the agent keeps collecting).- Everything the agent decides is recorded in its activity log, and any applied change is revertible.
Autopilot is intentionally conservative: it can broaden a starved agent's window or lower an over-restrictive floor, but it can never narrow collection or change your keywords without a proposal you can see and revert.
Team-level unlock. The "unlock autopilot by approving one proposal" step happens once per team, not once per agent. After your team has unlocked it, newly created agents come back already at autonomy_level: "autopilot" with autopilot_unlocked: true — expected behavior. Set any agent back to suggest via Set autonomy if you want manual approval.
Get activity
The agent's decision log — reflections, milestones, and the changes it has made over time. Free. Supports limit (max 100).
Request
curl -G https://api.virlo.ai/v1/agents/{agent_id}/activity \
-H "Authorization: Bearer {token}" \
-d limit=20
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
curl -G https://api.virlo.ai/v1/agents/{agent_id}/proposals \
-H "Authorization: Bearer {token}" \
-d status=pending
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
curl -X POST https://api.virlo.ai/v1/agents/{agent_id}/proposals/{proposal_id}/apply \
-H "Authorization: Bearer {token}"
Set autonomy
Choose how much the agent may change on its own.
- Name
autonomy_level- Type
- string
- Description
suggest(changes wait for approval) orautopilot(safe widenings auto-apply; must be unlocked first).
- Name
cognition_enabled- Type
- boolean
- Description
Master switch for self-optimization.
falsepauses all reflection + proposals.
Request
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" }'
