---
title: "Trigger or refresh an audience snapshot for a tracked creator"
method: POST
path: "/v1/tracking/creators/{id}/audience-refresh"
tags: ["Tracking"]
---

# Trigger or refresh an audience snapshot for a tracked creator

`POST /v1/tracking/creators/{id}/audience-refresh`

## Request

- Base URL: `https://api.virlo.ai`
- URL: `https://api.virlo.ai/v1/tracking/creators/{id}/audience-refresh`
- Auth: HTTP bearer

Cache-first. If a cached snapshot exists within freshness_days, it is returned synchronously and no credits are charged. Otherwise, an async job is enqueued and credits are reserved at the platform-specific rate (audience_snapshot_tiktok / youtube / instagram). If a snapshot job for this creator is already running, its job_id is returned and nothing is charged (credits_used: 0). Audience inference is currently in private preview — generation may take 60-120 seconds. Returns 409 Conflict when the creator's most recent tracking cycle failed or has not yet completed (audience reuses the same upstream provider and would be guaranteed to fail). Pass `{ "force": true }` to bypass this guard for transient platform-side failures.

## Path parameters

- `id` string, required

## Response `202`

Refresh requested

- AudienceRefreshResponseDto
  - `data` AudienceRefreshDataDto, required
    - `source` 'cache' | 'fresh', required: "cache" when the latest snapshot is reused, "fresh" when a new job was enqueued.
    - `snapshot` AudienceSnapshotDto
      - `snapshot_id` string, required
      - `snapshot_at` string, required
      - `sample_size` number, required: Number of commenter samples resolved for this snapshot
      - `age_distribution` object, nullable: Age band → share of resolved sample (0–1). Keys: "13-17", "18-24", "25-34", "35-44", "45+". Null when confidence is below internal floor.
      - `gender_distribution` object, nullable: Gender → share of resolved sample (0–1). Keys: "female", "male", "unknown".
      - `country_distribution` AudienceGeoBucketDto[], nullable: Top countries, ordered by share descending. Low-volume tail entries (those backed by fewer than 2 underlying commenters) are consolidated into a single trailing `{ "name": "Other (N countries)", "pct": <sum> }` bucket so the distribution head stays readable on small samples.
        - `code` string: ISO 3166-1 alpha-2 code (countries) or two-letter country code the city belongs to. Omitted on the trailing "Other" bucket that aggregates the long tail.
        - `name` string, required: Display name. The trailing bucket may be returned as "Other (N countries)" / "Other (N cities)" when low-volume tail entries are consolidated.
        - `pct` number, required: Share of the resolved sample (0–1)
        - `confidence` number: Aggregate confidence the pipeline has in this bucket assignment (0–1). Combines per-commenter inference confidence and corroborating signals. Omitted on the trailing "Other" bucket.
      - `city_distribution` AudienceGeoBucketDto[], nullable: Top cities, ordered by share descending. Long-tail consolidation matches `country_distribution` — singletons are rolled into a trailing `{ "name": "Other (N cities)", "pct": <sum> }` bucket. May be null when city signal is too sparse.
        - `code` string: ISO 3166-1 alpha-2 code (countries) or two-letter country code the city belongs to. Omitted on the trailing "Other" bucket that aggregates the long tail.
        - `name` string, required: Display name. The trailing bucket may be returned as "Other (N countries)" / "Other (N cities)" when low-volume tail entries are consolidated.
        - `pct` number, required: Share of the resolved sample (0–1)
        - `confidence` number: Aggregate confidence the pipeline has in this bucket assignment (0–1). Combines per-commenter inference confidence and corroborating signals. Omitted on the trailing "Other" bucket.
      - `language_distribution` object, nullable: Language code → share of resolved sample (0–1). ISO 639-1 lowercase codes.
      - `confidence_per_signal` AudienceConfidenceDto, required
        - `age` number
        - `gender` number
        - `country` number
        - `city` number
        - `language` number
      - `confidence_level` 'low' | 'medium' | 'high', required: Coarse confidence band. "high" = ≥200 samples with strong signals; "medium" = ≥100 or "high" downgraded (weak signals OR followers-only sample); "low" = ≥30 samples. Quick-take on snapshot reliability before reading `confidence_per_signal`.
      - `data_source` 'comments' | 'mixed' | 'followers' | 'comments_extended', required: How the sample was assembled. "comments" — engagement-only sample (default); "mixed" — comments + follower list blend (TikTok Tier 1 fallback when comments are sparse); "followers" — follower-list only (TikTok Tier 2 fallback, indicates audience composition without active engagement); "comments_extended" — comments re-collected over an extended post window (IG/YT fallback). Use to weight signals: comment-derived samples reflect active engagement; follower-derived samples reflect who chose to subscribe.
      - `signal_breakdown` object, required: Per-source contribution to `sample_size`. Always sums to `sample_size`. For `data_source = "followers"` snapshots, `comments` is 0; for `data_source = "comments"` snapshots, `followers` is 0; for `mixed` both are non-zero.
        - `comments` number
        - `followers` number
      - `evidence_summary` string, nullable: Human-readable summary of how the inferences were derived. Calls out when the sample is follower-sourced rather than commenter-sourced.
      - `model_version` string, required: Audience inference model version.
    - `job_id` string, nullable: Present when source = "fresh". Poll via GET /v1/tracking/creators/:id/audience-refresh/:job_id.
    - `status` string, nullable: Present when source = "fresh".
    - `credits_used` number, required: Credits deducted for triggering a fresh snapshot. Flat 50 credits ($0.50) on any platform. Zero when source = "cache", and zero when a snapshot job for this creator was already running (its job_id is returned). If the job ultimately fails with INSUFFICIENT_SAMPLE (even after follower / extended-window fallback), the 50 credits are automatically refunded; see the refund row in your usage history.
    - `creator_id` string: The tracked-creator UUID the refresh was triggered for. Round-trips the path parameter so consumers that hand this response off to a downstream tool retain the identifier without re-parsing the URL.
    - `credit_unit` string: Unit for `credits_used`. Always `"cent"` for audience-refresh — every credit is exactly $0.01 USD, so `credits_used: 50` means $0.50. Disambiguates the number for renderers that previously displayed "0 credits" on cache hits.
    - `finalized` boolean: True when the response carries the final snapshot (cache hit, no further work). False when `job_id` was just enqueued and `pending_jobs[0]` describes the live job.
    - `pending_jobs` PendingJobDto[]: Empty on cache hits. On `source: "fresh"` contains the single `audience_demographics` entry pointing at `/v1/audience/snapshot/:job_id` — agents can drive a uniform polling loop regardless of which trigger endpoint they used.
      - `type` 'viral_analysis' | 'audience_demographics' | 'audience_geography' | 'tracking_report', required: Stable identifier for the kind of secondary job. Use this to switch on the field where the result will appear (see `result_path`).
      - `status` 'pending' | 'processing', required: Current non-terminal state of the job. Once the job reaches a terminal state (ready / failed / skipped) the entry is removed from `pending_jobs[]`.
      - `job_id` string: Optional opaque identifier for the underlying job, when one exists. Useful with `poll_url` to fetch the result directly.
      - `poll_url` string: Path (relative to api.virlo.ai) you can GET to fetch the result of this job once it is ready. Free to call.
      - `result_path` string: Dot-path inside the parent resource's response body where the completed result will appear. Useful for clients that re-fetch the resource instead of using `poll_url`.
      - `webhook_event` string: Name of an event from the existing `SUPPORTED_EVENTS` list that fires when this job reaches a terminal state. Subscribe to it via POST /v1/webhooks to avoid polling. Guaranteed to be one of the already-supported event names so existing webhook subscriptions keep working — no new event types were introduced.
      - `started_at` string: When the job entered its current state (ISO 8601 UTC).
      - `retry_after_seconds` number: Suggested minimum delay (seconds) before polling again. Respect this to avoid hitting rate limits.

## Other responses

- `401`: Unauthorized - missing or invalid API key
- `402`: Insufficient credits for the platform-specific feature cost
- `404`: Tracked creator not found
- `409`: Tracking unhealthy — the creator has not had a successful scrape cycle yet, or the most recent cycle failed. Pass `force: true` to override.
- `429`: Rate limit exceeded (`rate_limit_exceeded`); see the Retry-After header

---

[API](https://skmtc.dev/virlo/apis/virlo-public-api.md) · [All operations](https://skmtc.dev/virlo/apis/virlo-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/virlo/apis/virlo-public-api/revisions/3b69c6145087?raw)
