How results load

Agents, lookups, tracking, and audience snapshots keep working after you start them. Here is how to tell when results are ready.

At a glance
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_seconds says. Or a webhook message telling you when to check.
You get back
finalized: true once everything is ready, even if status says completed. A few jobs only have status (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.

JobCheck (free GET)Done when
Content Research Agent/v1/agents/:idfinalized: true
Creator lookup/v1/satellite/creator/status/:job_idfinalized: 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_idstatus 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/:idfinalized: true
Post collection/v1/tracking/creators/:id/posts/collect/:collection_idstatus is completed or failed
Tracked video/v1/tracking/videos/:idenrichment_status is ready or failed
Audience snapshot/v1/audience/snapshot/:job_idfinalized: 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), or audience_demographics and audience_geography (one audience snapshot job).

  • Name
    status
    Type
    string
    Required
    *
    Description

    pending or processing.

  • 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 (data is 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

    15 today.

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: the intelligence object 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. After finalized, re-fetch a few times, then use the ready videos.
  • skipped: Virlo chose not to analyze it, so stop checking. intelligence_skip_reason says why: intent_mismatch, too_old, or under_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:

stageprogress_pcteta_seconds
collecting40null
analyzing (AI report)75Counts down from about 900, a worst case
finalizing90null
completed1000

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.

Was this page helpful?