How results load
Agents, lookups, tracking, and audience snapshots keep working after you start them. Here is how to tell when results are ready.
- What it does
- Tells you when a background job's results are ready.
- You send
- A free status check (polling) every 15 seconds, or whatever
retry_after_secondssays. Or a webhook message telling you when to check. - You get back
finalized: trueonce everything is ready, even ifstatussayscompleted. A few jobs only havestatus(see the table).- Cost
- Checking is free. You pay when a job starts, and for each recurring agent run or tracking check.
- How long
- About 30 seconds for a creator lookup. Usually under 20 minutes for a Content Research Agent to collect, plus a few for its AI report.
Where to check on each job
Replace each :placeholder with its ID.
| Job | Check (free GET) | Done when |
|---|---|---|
| Content Research Agent | /v1/agents/:id | finalized: true |
| Creator lookup | /v1/satellite/creator/status/:job_id | finalized: true |
Creator lookup's Data Intelligence (about 3 min after finalized) | /v1/satellite/runs/:run_id (your job_id) | intelligence_status is ready, failed, or skipped |
| Batch creator lookup | /v1/satellite/creators/batch/:batch_id | status is completed or failed |
| Video outlier, sound, or hashtag lookup | /v1/satellite/<type>/status/:job_id (video-outlier, sounds, hashtags) | status is completed or failed |
| Tracked creator | /v1/tracking/creators/:id | finalized: true |
| Post collection | /v1/tracking/creators/:id/posts/collect/:collection_id | status is completed or failed |
| Tracked video | /v1/tracking/videos/:id | enrichment_status is ready or failed |
| Audience snapshot | /v1/audience/snapshot/:job_id | finalized: true |
After 24 hours, a lookup's status check returns 404. Use /v1/satellite/runs/:run_id with the same ID.
The done signal (finalized)
finalized turns true once the main job and every follow-up job, such as the AI report, have stopped. Until then, empty (null) fields mean "not ready", not "nothing found". This agent has finished collecting but is still writing its report:
Report still being written
{
"data": {
"id": "df04c83b-7104-4579-bb4d-43f64c392c87",
"analysis": null,
"pending_jobs": [
{
"type": "viral_analysis",
"status": "processing",
"poll_url": "/v1/agents/df04c83b-7104-4579-bb4d-43f64c392c87",
"result_path": "data",
"webhook_event": "content_research_agent.run.completed",
"retry_after_seconds": 15
}
],
"finalized": false,
"stage": "analyzing",
"progress_pct": 75,
"eta_seconds": 893,
"latest_run": { "status": "completed" }
}
}
Finished is not the same as worked. First check that the job's status is not failed: latest_run.status on agents, enrichment_status on tracked creators, status elsewhere. On agents, partial_failure means one keyword or platform failed. The rest is usable.
It can turn back to false when a recurring agent's next run or a tracked creator's next check starts.
It doesn't cover per-video Data Intelligence. An agent's outlier-creator list can also shift for about a minute afterward.
What is still running (pending_jobs)
pending_jobs lists follow-up work still queued or running. Jobs leave the list when they finish or fail, as does an AI report not started 15 minutes after collection ends. A failed job's result stays null. So an agent with finalized: true and analysis: null has no report coming for this run, but its videos are still good.
An empty list does not mean done. An agent that is still collecting shows finalized: false with pending_jobs: []. So does an audience snapshot job while its status is processing.
Audience snapshots have two traps. A failed snapshot never lets a creator lookup's finalized turn true. During a refresh, a tracked creator's audience results show the old snapshot with finalized: true. So check the audience job, GET /v1/audience/snapshot/:job_id (on a lookup, if an audience field still reads processing after 15 minutes). It ends completed or failed, and failures are refunded. For tracked creators, the tracking route works too, with friendlier field names.
Full field reference
- Name
type- Type
- string
- Required
- *
- Description
viral_analysis(an agent's AI report),tracking_report(a tracked creator's AI report), oraudience_demographicsandaudience_geography(one audience snapshot job).
- Name
status- Type
- string
- Required
- *
- Description
pendingorprocessing.
- Name
job_id- Type
- string
- Description
Audience snapshot jobs only.
- Name
poll_url- Type
- string
- Description
Free path to check.
- Name
result_path- Type
- string
- Description
Where the result appears (
datais the whole object).
- Name
webhook_event- Type
- string
- Description
Event sent when it ends.
- Name
started_at- Type
- string
- Description
Not sent today.
- Name
retry_after_seconds- Type
- integer
- Description
15today.
Per-video breakdowns (intelligence_status)
With Data Intelligence (a paid breakdown of the videos worth studying), every video and slideshow an agent returns has an intelligence_status:
ready: theintelligenceobject is filled in. Agents with Data Intelligence off also get this, free, on videos Virlo already analyzed.pending: not analyzed yet, and it may never be. Afterfinalized, re-fetch a few times, then use thereadyvideos.skipped: Virlo chose not to analyze it, so stop checking.intelligence_skip_reasonsays why:intent_mismatch,too_old, orunder_followers(details).disabled: off for this agent. Turning it on only affects future runs: update a recurring agent, or create a new one.
Creator lookups (GET /v1/satellite/runs/:run_id) add failed (the run's analysis failed), and their skipped (this video got no analysis) carries no reason. The lookup's overall intelligence_status can be null for a few seconds after finalized. Check request.data_intelligence to see if you asked for it, and keep checking while the status is null or pending.
Slideshows leave out 16 video-only fields (details).
Waiting by polling
These functions wait for finalized: true. For other jobs, use the done condition in the table.
Wait until everything is ready
import time
import requests
def wait_until_finalized(url, headers, timeout_s=3600, default_wait_s=15):
started = time.time()
while time.time() - started < timeout_s:
res = requests.get(url, headers=headers)
res.raise_for_status()
data = res.json()["data"]
if data.get("finalized") is True:
return data
# Wait as long as the pending jobs suggest (15 seconds today).
pending = data.get("pending_jobs") or []
wait_s = max(
(job.get("retry_after_seconds") or default_wait_s for job in pending),
default=default_wait_s,
)
time.sleep(wait_s)
raise TimeoutError(f"{url} not finalized after {timeout_s} seconds")
agent = wait_until_finalized(
"https://api.virlo.ai/v1/agents/YOUR_AGENT_ID",
{"Authorization": "Bearer YOUR_API_KEY"},
)
# Finished is not the same as worked: check for a failed run first.
if (agent.get("latest_run") or {}).get("status") == "failed":
raise RuntimeError("The agent run failed, so there are no results")
print(agent["analysis"])
They stop after an hour. Raise that for large agents, but keep a limit.
Show progress while you wait
Agents also return stage, progress_pct, and eta_seconds on GET /v1/agents/:id and /summary, for a progress bar:
stage | progress_pct | eta_seconds |
|---|---|---|
collecting | 40 | null |
analyzing (AI report) | 75 | Counts down from about 900, a worst case |
finalizing | 90 | null |
completed | 100 | 0 |
Recurring agents often jump from collecting straight to completed.
Waiting with a webhook
A webhook is a message Virlo sends to your server when a job ends. Register one for all four events:
One webhook for all jobs
curl -X POST https://api.virlo.ai/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example.com/webhooks/virlo",
"description": "All finished jobs",
"enabled_events": [
"content_research_agent.run.completed",
"satellite.lookup.completed",
"tracking.cycle.completed",
"audience.snapshot.completed"
]
}'
Use your own public https:// address; the example gets a 400.
Messages are not copies of the GET response and have no finalized or pending_jobs. Some carry the full result, others only IDs. When one arrives, call the job's GET once. See event contents and retries.
Poll too. Messages can be late or lost, and some jobs send none: post collections, a lookup's Data Intelligence, failed audience snapshots, and free repeat lookups within 6 hours. A tracked item that can't be checked sends tracking.paused instead. Also check each job's GET every 10 minutes or so.

