# Data Intelligence

> AI labels the videos a Content Research Agent collects with 79 fields: topic, hook, format, tone, brand safety, who is on screen, AI use and more. Most videos get them, not all. Slideshows get 70. Costs $1.00 extra per run.

Source: https://dev.virlo.ai/docs/intelligence
Markdown: https://dev.virlo.ai/docs/intelligence.md
Section: Content Research Agents

## About the Virlo API (applies to every page)

- Base URL: `https://api.virlo.ai/v1`. Every request needs the header `Authorization: Bearer YOUR_API_KEY` (keys start with `virlo_tkn_`).
- Responses are JSON inside a `data` field, except the `/v1/webhooks` endpoints, which return the object or array directly. Field names are snake_case.
- Prices are in US dollars from a prepaid balance. 1 credit = $0.01. The `X-Cost` response header on each successful response is the exact charge. Errors are free.
- Slow jobs return an ID. Check its status every 15 seconds (or whatever `retry_after_seconds` says) until `finalized` is `true`.
- All docs pages: https://dev.virlo.ai/llms.txt. Every page in one file: https://dev.virlo.ai/llms-full.txt. MCP server for AI assistants: https://dev.virlo.ai/api/mcp/mcp.

---

Data Intelligence is an add-on for [Content Research Agents](https://dev.virlo.ai/docs/agents). AI labels most of the videos an agent collects with 79 fields, like hook, format, tone and brand safety. You can then compare videos by what is in them, not just by views.

**At a glance**

- **What it does:** Labels each video an agent collects: hook, format, tone, brand safety, who is on screen and more.
- **You send:** `data_intelligence_enabled: true` when you create an agent. A recurring agent can also turn it on later.
- **You get back:** An `intelligence` block on each video and slideshow. Videos also get `intent_match`: does this video fit your brief?
- **Cost:** $1.00 extra per run, so a run costs $1.50 instead of $0.50. Reading the results is free.
- **How long:** A run usually takes under 20 minutes. Fields keep arriving after it finishes, and some videos never get them.

---

## How it works

1. You create a [Content Research Agent](https://dev.virlo.ai/docs/agents) with `data_intelligence_enabled: true`.
2. The agent collects videos and slideshows for your keywords. Each round of collecting is a **run**.
3. AI reviews each one. It looks at still frames when it can, and reads the caption and what is said (for a slideshow, its images and their text). It writes what it finds into an `intelligence` block.
4. You read the results for free with [Get videos](https://dev.virlo.ai/docs/agents#get-agent-videos) and [Get slideshows](https://dev.virlo.ai/docs/agents#get-agent-slideshows).

Each video and slideshow also has an `intelligence_status` that says whether its fields are in yet. See [When fields are missing](https://dev.virlo.ai/docs/intelligence#when-fields-are-missing).

---

## What you get

The 79 fields fall into 14 groups:

| Group | What it tells you | Fields |
| - | - | - |
| Content | Topic, category and kind of content | 5 |
| Visual | Format, camera, setting, lighting | 11 |
| Hook and on-screen text | The opening line and the text on screen | 6 |
| Captions and transcript | Burned-in captions, spoken words, language | 7 |
| Tone | Mood, sentiment, speaking style | 3 |
| Brand safety | Safety tier, sensitive topics, sponsorship, brands named | 5 |
| Engagement tactics | Calls to action, social proof, trend references | 3 |
| Summary | Short summary, educational or not, what the AI was unsure about | 3 |
| People on screen | Who appears, apparent age and gender, faceless or on camera | 17 |
| AI use | Whether the video itself was made with AI | 4 |
| Brands and products on screen | Logos, products and characters in frame | 3 |
| Animals | Which animals appear and whether they are the subject | 3 |
| Screen and audio | Screen recordings, and whether anyone speaks | 2 |
| Edit structure | Compilations, rankings, reposts, before-and-after, watermarks | 7 |

You can't ask the API for only faceless videos, say. You sort the results yourself, in code or a spreadsheet ([Use cases](https://dev.virlo.ai/docs/intelligence#use-cases)). Only [`intent_match`](https://dev.virlo.ai/docs/intelligence#intent-matching) has a built-in filter.

> **Note:** [Creator lookups](https://dev.virlo.ai/docs/satellite/creators#data-intelligence) with `data_intelligence` get only the first eight groups: 43 fields per video, 35 per slideshow.

### Filters to apply yourself

| You want | Check that |
| - | - |
| Faceless videos | `presence_style` is `faceless_voiceover`, `faceless_silent` or `hands_or_pov` |
| A person talking to the camera | `presence_style` is `on_camera_presenter` and `is_silent` is `false` |
| No AI-made content | `ai_provenance` is `none` |
| Original posts only | `is_repost` and `is_compilation` are `false` |
| Sponsored posts | `is_sponsored` is `true` |
| An animal is the star | `animal_presence` is `animal_featured`. For pets, also check `animals_visible` has `dog`, `cat` or `other_pet` |
| No talking | `is_silent` is `true` |

`on_camera_presenter` means someone faces the camera and addresses it at some point, speaking or not. For stricter talking-head videos, where that is the main shot throughout, use `visual_format` `talking_head`.

---

## Pricing

Data Intelligence adds **$1.00** to each run, however many videos it collects.

| Agent type | Without | With Data Intelligence | When you pay |
| - | - | - | - |
| Runs once | $0.50 | **$1.50** | When you create it, even if the run later fails |
| Recurring (runs on a schedule) | $0.50 per run | **$1.50** per run | After each run finishes, starting with the first |

- **Recurring agents are free to create,** but creating one with Data Intelligence needs $1.50 in your balance. A run where some keywords or platforms failed (`partial_failure`) is still charged. A recurring run that fails outright is not charged.
- **Recurring charges are automatic.** Check them in your balance and usage history ([Billing and pricing](https://dev.virlo.ai/docs/credits)). No API response reports them (no `X-Cost` header).
- **Reading is free.** With Data Intelligence on, the agent's [hook list](https://dev.virlo.ai/docs/hooks#agent-hooks) is free too. Without it, each hook-list request costs $0.25, or nothing while the agent has no hooks yet.

---

## Turn it on

Add `data_intelligence_enabled: true` when you [create an agent](https://dev.virlo.ai/docs/agents#create-agent). The first run starts right away. In every example on this page, replace `YOUR_API_KEY` with your [API key](https://dev.virlo.ai/docs/authentication).

**cURL:**

```bash
curl -X POST https://api.virlo.ai/v1/agents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Skincare Routine Research",
    "is_recurring": false,
    "intent": "Find beginner glass-skin routines that name drugstore products, not dermatologist lectures",
    "keywords": ["glass skin routine", "skincare routine for beginners", "drugstore skincare routine"],
    "platforms": ["tiktok", "youtube"],
    "data_intelligence_enabled": true
  }'
```

A one-time agent can't run again, so turn it on at creation. A recurring agent can turn it on or off at any time with [Update agent](https://dev.virlo.ai/docs/agents#update-agent). The change applies from the next run, sets the price of future runs, and does not go back over earlier videos.

**cURL:**

```bash
curl -X PUT https://api.virlo.ai/v1/agents/{agent_id} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data_intelligence_enabled": true }'
```

---

## When fields are missing

`intelligence_status` on each video and slideshow tells you why `intelligence` might be `null`:

| `intelligence_status` | Meaning | What to do |
| - | - | - |
| `ready` | The fields are in. | Use them. |
| `pending` | Data Intelligence is on, but the fields aren't in yet. | Check again later. If it is still `pending` the next day, it will probably never get them. It never turns into "failed". |
| `disabled` | Data Intelligence is off for this agent. | Turn it on for future runs, or ignore the field. |

What to plan for:

1. **Not every video gets fields.** On one agent, about a third of its videos were still `pending` three days after the run. Almost all had `intent_match` `false`: videos that don't fit your intent usually skip the full review.
2. **`finalized: true` does not wait for the fields.** The agent can report its run as finished ([Get agent](https://dev.virlo.ai/docs/agents#get-agent)) while videos are still `pending`. Read again later to pick them up.
3. **YouTube videos often lack the picture fields.** When the AI can't get a video's frames, it works from the caption and speech only. Every field that needs the picture is then `null`: format, setting, camera, lighting, on-screen text, faceless or on camera, products, animals and watermarks. It can hit more than half of an agent's YouTube videos. You can spot them because `low_confidence_fields` includes `tier3_unavailable`.
4. **Turned it on later?** Videos from earlier runs show `pending` and usually stay that way.

**Details for developers**

- **Single-value fields can be `null` in a `ready` block** when the AI could not tell, including many true/false fields. For example, `hook_type` is `null` when the video has no opening line at all. A weak opening line gets `none`.
- **List fields are `[]`, never `null`.** `is_nsfw`, `is_sponsored`, `is_educational` and `is_multilingual` are never `null` either: they read `false` when nothing was found, so `false` can also mean "couldn't tell".
- **`low_confidence_fields` can hold names that aren't fields,** like `inferred_region`. Ignore names you don't recognize.
- **Videos flagged by Virlo's minor-safety check are dropped** from every list, and no field marks them. That is one reason a page can hold fewer videos than you asked for.
- **Fields without Data Intelligence.** A video Virlo already reviewed for another agent can come back `ready` at no charge. Don't count on it.

---

## Intent matching

Every agent has an `intent`: one plain sentence saying what you want to find. With Data Intelligence on, each video also gets `intent_match`, a yes or no answer to "does this video fit my intent?"

- AI decides from the caption, hashtags and spoken words when the video is collected. It never looks at the picture. So write your intent about the topic ("honest reviews of drugstore moisturizers"), not the look ("talking head"), and check the look with the [fields](https://dev.virlo.ai/docs/intelligence#what-you-get). The [Intent cookbook](https://dev.virlo.ai/docs/intent-cookbook) has more on writing intents.
- `intent_match` can arrive before `intelligence`. Videos that get `false` usually never get the other fields.
- Videos only. Slideshows don't get it.
- It sits next to `intelligence` on the video, not inside it. It is `null` when Data Intelligence was off when the video was collected, or the check failed.

**JSON:**

```json
{
  "id": "b3a1f892-7c4e-4d8a-9f12-6e8b4a2c1d05",
  "url": "https://www.tiktok.com/@creator/video/7392847561023456789",
  "views": 2847000,
  "intelligence": { ... },
  "intent_match": {
    "matches": true,
    "reasoning": "The caption 'my honest review after 30 days' and the transcript describe the creator testing a moisturizer and giving their own opinion, which fits the intent."
  }
}
```

- `matches` (boolean, optional): `true` if the video fits your intent.
- `reasoning` (string, optional): Why, usually one sentence quoting the caption, hashtags or transcript. Sometimes it is a machine string instead, like `jev subject=0.93 stuffing=0.05`, so check before showing it to clients. Don't parse it.

### Filter videos by intent match

Add `intent_match=true` (or `false`) to [Get videos](https://dev.virlo.ai/docs/agents#get-agent-videos):

```
GET /v1/agents/:id/videos?intent_match=true
```

> **Note:** Good for browsing, not counting. It filters one page at a time. A page can be short or empty while later pages still have matches, and `total` counts only that page (with `limit=1`, usually `0`). To count matches, read every page without the filter and count `intent_match.matches` yourself, as [Example 1](https://dev.virlo.ai/docs/intelligence#end-to-end-examples) does.

Ignore the `intent_summary` (`{ matched, total_evaluated }`) that some run-finished [webhooks](https://dev.virlo.ai/docs/webhooks) carry. It counts every video checked for the agent so far, not just that run.

---

## Use cases

The code below is for your developer. Each snippet takes `videos`, the list from [Get videos](https://dev.virlo.ai/docs/agents#get-agent-videos) (`response['data']['videos']`), and skips videos with no fields yet.

### Brand safety filtering

Drop risky videos before you repost creator content or place ads next to it.

**Python:**

```python
def is_brand_safe(v):
    intel = v.get('intelligence')
    if not intel:
        return False  # not reviewed yet, so not confirmed safe
    topics = set(intel.get('sensitive_topics') or [])
    return (
        intel.get('brand_safety_tier') in ('safe', 'low_risk')
        and not intel.get('is_nsfw')
        and topics <= {'none'}  # nothing sensitive: the list is empty or only 'none'
    )

safe_videos = [v for v in videos if is_brand_safe(v)]
```

### Which hooks show up most

Count the most common kinds of opening line in your niche. Common is not the same as successful: to see which hooks get the most views, group by `hook_type` the way the next snippet groups by format. [Hooks](https://dev.virlo.ai/docs/hooks#hook-types-explained) explains each hook type.

**Python:**

```python
from collections import Counter

hook_counts = Counter(
    v['intelligence']['hook_type']
    for v in videos
    if v.get('intelligence') and v['intelligence'].get('hook_type')
)
print(hook_counts.most_common(5))
```

### Which formats get the most views

Compare one platform at a time, and use the median, not the average, so one viral hit doesn't skew a format.

**Python:**

```python
from statistics import median

# (platform, format) -> view counts
views = {}
for v in videos:
    fmt = (v.get('intelligence') or {}).get('visual_format')
    if fmt:
        views.setdefault((v['platform'], fmt), []).append(v['views'] or 0)

median_views = {
    key: median(counts)
    for key, counts in views.items()
    if len(counts) >= 5  # skip formats with too few videos to judge
}
# each platform, best format first
for (platform, fmt), mid in sorted(median_views.items(), key=lambda kv: (kv[0][0], -kv[1])):
    print(platform, fmt, int(mid))
```

### Competitor brand mentions

`brands_mentioned` comes from what is said and written. `brands_visible` comes from logos and products on screen. This counts each brand once per video.

**Python:**

```python
from collections import Counter

brand_counts = Counter()
for v in videos:
    intel = v.get('intelligence') or {}
    brands = set(intel.get('brands_mentioned') or []) | set(intel.get('brands_visible') or [])
    brand_counts.update(brands)

print(brand_counts.most_common(10))
```

### Find one exact format: text-story videos

Some formats are easier to find by look than by keyword. Text-story videos show a person, nobody talks, and more than 50 words of on-screen text tell the story. Use the agent's [intent](https://dev.virlo.ai/docs/intelligence#intent-matching) for the topic and these fields for the look.

**Python:**

```python
def is_text_story(v):
    intel = v.get('intelligence') or {}
    overlay = intel.get('text_overlay_content') or ''
    return (
        intel.get('has_face_visible') is True
        and intel.get('is_silent') is True
        and len(overlay.split()) > 50
    )

text_stories = [v for v in videos if is_text_story(v)]
```

---

## End-to-end examples

These use Python and the `requests` library. Every response wraps its results in a `data` object, which is why the code reads `['data']`.

### Example 1: Find talking-head product reviews

A brand team wants reviews where a creator talks to the camera, for a user-generated content (UGC) campaign. `intent_match` checks the topic. The fields check the look.

**Step 1: Create the agent.** This costs $1.50.

**Python:**

```python
import time
import requests

API = 'https://api.virlo.ai/v1'
HEADERS = {'Authorization': 'Bearer YOUR_API_KEY'}

agent = requests.post(f'{API}/agents', headers=HEADERS, json={
    'name': 'UGC Talking Head Reviews',
    'is_recurring': False,
    'intent': (
        'Honest product reviews where a creator tries a product and gives '
        'their own opinion, not paid ads or sponsored posts'
    ),
    'keywords': ['honest product review', 'trying this product', 'is it worth it review'],
    'platforms': ['tiktok', 'instagram'],
    'data_intelligence_enabled': True,
}).json()

agent_id = agent['data']['id']
```

**Step 2: Wait for the run to finish.** Check the agent again every 30 seconds (this is called polling) until `finalized` is `true`. Give up after about 45 minutes, or if the latest run's `status` is `failed`.

**Python:**

```python
deadline = time.time() + 45 * 60  # give up after 45 minutes
while True:
    info = requests.get(f'{API}/agents/{agent_id}', headers=HEADERS).json()['data']
    if (info.get('latest_run') or {}).get('status') == 'failed':
        raise RuntimeError('The run failed. Check the agent before trying again.')
    if info.get('finalized') is True:
        break
    if time.time() > deadline:
        raise TimeoutError('The run is still going after 45 minutes.')
    time.sleep(30)
```

**Step 3: Read every video.** Ask for 100 per page until a page comes back empty. A page can hold fewer than 100 even when more remain.

**Python:**

```python
def all_videos(agent_id):
    """Yield every video the agent collected, 100 per page."""
    seen = set()
    page = 1
    while True:
        batch = requests.get(
            f'{API}/agents/{agent_id}/videos',
            headers=HEADERS,
            params={'limit': 100, 'page': page},
        ).json()['data']['videos']
        if not batch:
            return
        for video in batch:
            if video['id'] not in seen:  # pages can overlap, so skip videos we have already seen
                seen.add(video['id'])
                yield video
        page += 1

videos = list(all_videos(agent_id))
```

Some videos can still show `intelligence_status: pending` right after the run. Run Steps 3 and 4 again later to pick them up.

**Step 4: Keep the videos that fit.**

**Python:**

```python
def is_talking_head_review(v):
    intel = v.get('intelligence') or {}
    fits_intent = (v.get('intent_match') or {}).get('matches') is True
    return (
        fits_intent
        and intel.get('presence_style') == 'on_camera_presenter'
        and intel.get('is_silent') is False
        and intel.get('is_sponsored') is False
    )

picks = [v for v in videos if is_talking_head_review(v)]
print(f'{len(picks)} of {len(videos)} videos fit')
for v in picks[:10]:
    print(v['url'], '|', v['intent_match']['reasoning'])
```

### Example 2: Daily brand-safety audit

A media buyer checks a fitness niche every day for videos that would be unsafe next to supplement ads. `cadence: 'daily'` with Data Intelligence costs $1.50 a day.

**Python:**

```python
agent = requests.post(f'{API}/agents', headers=HEADERS, json={
    'name': 'Daily Brand Safety Audit - Fitness',
    'is_recurring': True,
    'cadence': 'daily',
    'intent': (
        'Everyday creators sharing their own fitness transformation or '
        'weight loss journey, not gym ads or supplement promos'
    ),
    'keywords': ['fitness transformation', 'weight loss journey', 'body transformation progress'],
    'platforms': ['tiktok', 'youtube'],
    'data_intelligence_enabled': True,
}).json()

agent_id = agent['data']['id']
```

After each run (when `last_run_at` changes or a webhook arrives, see Example 3), split the videos into safe and flagged. This checks every video collected so far. To report only new ones, skip IDs you already checked. It reuses `is_brand_safe` ([Brand safety filtering](https://dev.virlo.ai/docs/intelligence#brand-safety-filtering)) and `all_videos` (Example 1).

**Python:**

```python
safe, flagged = [], []
for v in all_videos(agent_id):
    if not v.get('intelligence'):
        continue  # no fields yet
    if is_brand_safe(v):
        safe.append(v)
    else:
        flagged.append(v)

print(f'Brand-safe: {len(safe)}  Flagged: {len(flagged)}')
for v in flagged[:10]:
    intel = v['intelligence']
    print(v['url'], intel['brand_safety_tier'], intel['sensitive_topics'])
```

### Example 3: Start work from a webhook

A [webhook](https://dev.virlo.ai/docs/webhooks) lets Virlo tell your server when a run finishes, so you don't have to poll. React to `content_research_agent.run.completed`:

**Python:**

```python
from flask import Flask, request

app = Flask(__name__)

@app.post('/webhooks/virlo')
def handle_webhook():
    payload = request.json
    if payload['event'] == 'content_research_agent.run.completed':
        # Reply fast. Do the slow work (reading videos) in a background job.
        schedule_intent_review(payload['data'])
    return '', 200
```

`schedule_intent_review` is your own code that reads and filters videos as in Example 1. The agent's ID is not at the top of `data`, and where it sits depends on how the run ended. [Agent run finished](https://dev.virlo.ai/docs/webhooks#agent-run-payload) shows the one-line lookup. The webhook can arrive before every video's fields are in, so read again later if many are still `pending`.

---

## Field reference

All 79 fields sit in the `intelligence` object on a video. `enum` means one value from a fixed list (see [Allowed values](https://dev.virlo.ai/docs/intelligence#enum-values)), `object` is a small record, and `[]` means a list. See [When fields are missing](https://dev.virlo.ai/docs/intelligence#when-fields-are-missing) for when a field is `null`.

**All 79 video fields**

### Content

- `primary_topic` (string, optional): Main topic.
- `secondary_topics` (string[], optional): Other topics it covers.
- `keywords` (string[], optional): Keywords that describe the content.
- `category` (string, optional): Broad subject area, like `beauty`. Usually one of the [28 listed values](https://dev.virlo.ai/docs/intelligence#enum-values).
- `content_format` (string, optional): Kind of content, like `tutorial` or `review`. Usually one of the [52 listed values](https://dev.virlo.ai/docs/intelligence#enum-values).

### Visual

- `visual_format` (enum, optional): Main visual style, like `talking_head` or `screen_recording`.
- `visual_complexity` (enum, optional): How busy the picture is.
- `camera_perspective` (enum, optional): Camera angle and position.
- `setting` (enum, optional): Where it was filmed.
- `lighting_quality` (enum, optional): Lighting quality.
- `has_face_visible` (boolean, optional): A human face is visible.
- `scene_changed` (boolean, optional): The scene changes during the video.
- `background_type` (enum, optional): What the background is.
- `background_reasoning` (string, optional): Why it chose that `background_type`.
- `foreground_type` (enum, optional): What the main subject in front is.
- `foreground_reasoning` (string, optional): Why it chose that `foreground_type`.

### Hook and on-screen text

- `hook_text` (string, optional): The opening line, spoken or on screen.
- `hook_type` (enum, optional): Kind of hook, like `question` or `bold_claim`.
- `visual_hook_type` (enum, optional): What the opening shot shows.
- `has_text_overlay` (boolean, optional): Text is shown on screen.
- `text_overlay_purpose` (enum, optional): What the main on-screen text is for.
- `text_overlay_content` (string, optional): The main on-screen text, word for word.

### Captions and transcript

- `has_onscreen_captions` (boolean, optional): Burned-in captions are shown.
- `caption_style` (enum, optional): Style of those captions.
- `transcript_quality` (enum, optional): How clean the transcript of the spoken words is.
- `transcript_word_count, transcript_character_count` (integer, optional): Size of the transcript. `0` when nobody speaks or no transcript was made.
- `language_detected` (string, optional): Two-letter language code, like `en`.
- `is_multilingual` (boolean, optional): More than one language is spoken.

### Tone

- `emotional_tone` (enum, optional): Main mood.
- `sentiment` (enum, optional): Positive, negative, neutral or mixed.
- `speaking_style` (enum, optional): How the speaker talks.

### Brand safety

- `brand_safety_tier` (enum, optional): How safe it is for brands, from `safe` to `unsafe`.
- `is_nsfw` (boolean, optional): Not safe for work.
- `sensitive_topics` (enum[], optional): Sensitive topics found. `["none"]` when there are none.
- `is_sponsored` (boolean, optional): Sponsored content.
- `brands_mentioned` (string[], optional): Brands named in what is said or written.

### Engagement tactics

- `cta_usages` (object[], optional): Calls to action, each `{ type, text }`. `text` is the exact wording.
- `social_proof_used` (enum[], optional): Social proof used, like testimonials or statistics.
- `trend_references` (string[], optional): Trends or challenges it refers to.

### Summary

- `summary` (string, optional): Short summary, usually 2 or 3 sentences.
- `is_educational` (boolean, optional): The video mainly teaches something.
- `low_confidence_fields` (string[], optional): Fields the AI was unsure about. `tier3_unavailable` here means the frames were not analyzed.

### People on screen

Read from the picture. These describe how people **appear**, not who they are, and are empty or `null` when nobody is detected.

- `people` (object[], optional): One record per person: `{ role, is_synthetic, apparent_gender, apparent_age_bracket, apparent_age_min, apparent_age_max, confidence }`.
- `presence_style` (enum, optional): Faceless or on camera. Combines `on_screen_presence` with whether anyone speaks. Use this one for faceless filters.
- `on_screen_presence` (enum, optional): What the camera shows of a person, from the picture only.
- `people_source` (enum, optional): How `people` was worked out: from frames, from the words, or not at all.
- `people_count_bucket` (enum, optional): Roughly how many people appear.
- `primary_subject_gender` (enum, optional): Apparent gender of the main person.
- `primary_subject_age_bracket` (enum, optional): Apparent age bracket of the main person.
- `primary_subject_age_min, primary_subject_age_max` (integer, optional): Estimated age range of the main person.
- `primary_subject_confidence` (enum, optional): How sure the AI is about the main person.
- `genders_present` (enum[], optional): Every apparent gender on screen.
- `age_brackets_present` (enum[], optional): Every apparent age bracket on screen.
- `subject_gender_skew` (enum, optional): Overall gender mix.
- `has_minor_visible` (boolean, optional): Someone who appears under 18 is on screen.
- `has_real_person` (boolean, optional): A real, not AI-made, person appears.
- `has_baby_visible` (boolean, optional): A baby (0 to 3) appears.
- `has_senior_visible` (boolean, optional): Someone 65 or older appears.

### AI use

Whether the video was **made** with AI. A tutorial about ChatGPT is not AI-made.

- `ai_provenance` (enum, optional): How much of the video is AI-made, from `none` to `fully_ai`.
- `ai_elements` (enum[], optional): Each AI-made part found. Videos can mix them, like a real creator with an AI voiceover.
- `ai_confidence` (enum, optional): How sure the AI-use answer is.
- `ai_reasoning` (string, optional): Why it chose that `ai_provenance`.

### Brands and products on screen

From the **picture**, so they catch a logo nobody mentions.

- `brands_visible` (string[], optional): Brand names and logos seen in frame.
- `product_presence` (enum, optional): Whether a product is the subject, and whether it is branded.
- `ip_characters` (string[], optional): Known characters, like franchise characters or mascots.

### Animals

- `animals_visible` (enum[], optional): Kinds of animal on screen.
- `animal_presence` (enum, optional): Whether an animal is the subject or just in the background.
- `has_animal` (boolean, optional): Any animal appears.

### Screen and audio

- `screen_content_type` (enum, optional): What a recorded screen shows. `null` unless a screen fills much of the picture.
- `is_silent` (boolean, optional): Nobody speaks (text or music only).

### Edit structure

- `is_compilation` (boolean, optional): Unrelated clips stitched together.
- `is_ranking` (boolean, optional): A ranking or tier list, like "top 5".
- `is_repost` (boolean, optional): Reuploaded content rather than original.
- `is_before_after` (boolean, optional): A before-and-after transformation.
- `is_layered_composition` (boolean, optional): Visual layers stacked, like a reaction over a clip.
- `has_platform_watermark` (boolean, optional): A platform or editing-app watermark is visible.
- `watermark_source` (string, optional): Where the watermark is from, like `tiktok` or `capcut`.

`presence_style`, `subject_gender_skew`, `ai_provenance` and the `has_*` people and animal fields are calculated from the other fields, so they always agree with them. Filter on these instead of digging through `people`.

---

## Slideshow intelligence

Slideshows are photo carousels, mostly from TikTok. Each gets its own `intelligence` block with **70 fields** and the same `intelligence_status` values as videos. They drop 16 video fields about motion, camera work and speech, and add 7 about the slides, which Virlo calls panels. The other 63 fields work as on videos.

- `narrative_arc` (enum, optional): How the panels tell the story, like `listicle`, `tutorial_steps` or `before_after`.
- `text_density` (enum, optional): Whether text or images carry the post: `text_dominant`, `balanced` or `image_dominant`.
- `image_count` (integer, optional): Panels analyzed, up to 10. The slideshow's `images` list can hold more.
- `panel_texts` (string[], optional): The text on each panel, in order.
- `panel_text_full` (string, optional): All panel text in one string, labeled `Panel 1: ...`, `Panel 2: ...`. Can be `null`.
- `panel_text_word_count, panel_text_character_count` (integer, optional): Words and characters in `panel_text_full`.

Video fields slideshows don't have: `visual_format`, `camera_perspective`, `lighting_quality`, `visual_complexity`, `scene_changed`, `visual_hook_type`, `has_text_overlay`, `text_overlay_content`, `text_overlay_purpose`, `has_onscreen_captions`, `caption_style`, `transcript_quality`, `transcript_word_count`, `transcript_character_count`, `speaking_style`, `is_layered_composition`.

Slideshows don't get `intent_match`, so the `intent_match` filter does nothing on [Get slideshows](https://dev.virlo.ai/docs/agents#get-agent-slideshows).

**Example slideshow intelligence (shortened)**

This leaves out the people, AI use, brands on screen, animals, screen and edit structure fields. Real slideshows include them.

**JSON:**

```json
{
  "intelligence_status": "ready",
  "intelligence": {
    "primary_topic": "Minimal morning skincare routine for oily skin",
    "secondary_topics": ["niacinamide benefits", "SPF layering"],
    "keywords": ["skincare", "oily skin", "morning routine"],
    "category": "beauty",
    "content_format": "tutorial",
    "narrative_arc": "tutorial_steps",
    "text_density": "balanced",
    "image_count": 5,
    "panel_texts": [
      "Stop wasting money on a 12-step routine",
      "Step 1: gentle cleanser",
      "Step 2: niacinamide serum",
      "Step 3: lightweight moisturizer",
      "Step 4: SPF 50"
    ],
    "panel_text_full": "Panel 1: Stop wasting money on a 12-step routine\n\nPanel 2: Step 1: gentle cleanser\n\nPanel 3: Step 2: niacinamide serum\n\nPanel 4: Step 3: lightweight moisturizer\n\nPanel 5: Step 4: SPF 50",
    "panel_text_word_count": 33,
    "panel_text_character_count": 185,
    "language_detected": "en",
    "is_multilingual": false,
    "emotional_tone": "inspiring",
    "sentiment": "positive",
    "has_face_visible": false,
    "background_type": "real_world_photo",
    "background_reasoning": "Product photos on a bathroom counter fill every panel.",
    "foreground_type": "real_photo_subject",
    "foreground_reasoning": "Each panel centers one skincare product with a text caption.",
    "setting": "indoor_bathroom",
    "hook_text": "Stop wasting money on a 12-step routine",
    "hook_type": "bold_claim",
    "brand_safety_tier": "safe",
    "is_nsfw": false,
    "sensitive_topics": ["none"],
    "is_educational": true,
    "is_sponsored": false,
    "brands_mentioned": ["CeraVe", "La Roche-Posay"],
    "cta_usages": [
      { "type": "link_in_bio", "text": "products linked below" }
    ],
    "trend_references": ["skin minimalism"],
    "social_proof_used": [],
    "summary": "Four-step morning routine for oily skin, presented as a numbered carousel with affordable product picks.",
    "low_confidence_fields": []
  }
}
```

---

## Allowed values

A field typed `enum` takes one value from these lists. New values may be added, so handle ones you don't recognize. `category` and `content_format` are looser: the AI usually picks from their lists, but other values can appear.

**All allowed values**

### category

`art_design`, `automotive`, `beauty`, `business_career`, `crafts_diy`, `education`, `entertainment`, `fashion`, `finance`, `fitness`, `food_beverage`, `gaming`, `health_wellness`, `home_garden`, `kids_content`, `lifestyle`, `music`, `news_politics`, `parenting_family`, `pets`, `real_estate`, `relationships_dating`, `religion_spirituality`, `science_nature`, `sports`, `tech`, `travel`, `other`

### content_format

`tutorial`, `storytime`, `rant`, `review`, `comedy_bit`, `motivational`, `news_commentary`, `news_report`, `reaction`, `listicle`, `challenge`, `day_in_life`, `q_and_a`, `explainer`, `educational_breakdown`, `hot_take`, `unboxing`, `transformation`, `commentary_voiceover`, `grwm_routine`, `haul_restock`, `cooking_recipe`, `workout_demo`, `meditation`, `asmr`, `time_lapse`, `skit_sketch`, `lip_sync_dance`, `trend_performance`, `prank`, `experiment`, `street_interview`, `reddit_reading_storytime`, `silent_aesthetic`, `location_showcase`, `travel_guide`, `gameplay_commentary`, `gameplay_lets_play`, `stream_highlight`, `podcast_clip`, `interview_clip`, `ranking`, `compilation`, `tier_list`, `documentary_short`, `fancam_edit`, `testimonial`, `product_demo`, `ad_creative`, `pov_scenario`, `quiz_or_test`, `other`

### background_type

`solid_color`, `illustrated_scene`, `real_world_photo`, `digital_screen_capture`

A phone filmed on a desk is `real_world_photo`. Only direct screenshots are `digital_screen_capture`.

### foreground_type

`text_only`, `real_photo_subject`, `illustrated_subject`, `meme`, `chart_or_data_ui`, `none_or_minimal`

### hook_type

`question`, `bold_claim`, `shock_statement`, `story_tease`, `tutorial_promise`, `controversy`, `before_after`, `pov_setup`, `statistic`, `direct_address`, `trend_reference`, `cliffhanger`, `negation`, `relatable_scenario`, `comparison`, `mystery_setup`, `none`

### speaking_style

`conversational`, `formal`, `hype_energy`, `whisper_asmr`, `voiceover_narration`, `comedic`, `storytelling`, `instructional`, `monotone`, `aggressive`, `deadpan`, `shouting`, `flirty`

### emotional_tone

`funny`, `inspiring`, `shocking`, `educational`, `controversial`, `heartwarming`, `wholesome`, `angry`, `sad`, `hype`, `calm`, `sarcastic`, `nostalgic`, `cringe`, `dark_humor`, `urgent`, `mysterious`, `relatable`, `neutral`

### sentiment

`positive`, `negative`, `neutral`, `mixed`

### cta_usages type

`follow`, `subscribe`, `like_video`, `comment`, `share`, `save_post`, `tag_friend`, `link_in_bio`, `visit_website`, `dm_message`, `buy`, `pre_order`, `download`, `sign_up`, `use_code`, `enter_giveaway`, `vote`, `book_appointment`

### social_proof_used

`testimonial`, `before_after_results`, `statistic_cited`, `celebrity_mention`, `expert_endorsement`, `popularity_claim`, `user_count`, `award_mention`

### sensitive_topics

`mild_profanity`, `strong_profanity`, `violence_described`, `drug_reference`, `alcohol`, `tobacco_vaping`, `gambling`, `controversial_politics`, `religious_discussion`, `mental_health`, `eating_disorders`, `self_harm_reference`, `sexual_content`, `hate_speech`, `medical_claims`, `financial_advice`, `weapons`, `none`

`["none"]` means nothing sensitive was found. `none` can also appear next to other values, so check for anything other than `none`.

### brand_safety_tier

`safe`, `low_risk`, `medium_risk`, `high_risk`, `unsafe`

### transcript_quality

`clean`, `partial`, `garbled`

### visual_format

`talking_head`, `pov_footage`, `interview`, `street_interview`, `screen_recording`, `text_messaging_thread`, `slideshow_text`, `animation_motion_graphics`, `b_roll_montage`, `whiteboard_presentation`, `vlog_handheld`, `activity_demonstration`, `product_closeup`, `green_screen_commentary`, `split_screen_duet`, `gameplay_background`, `native_gameplay`, `stream_overlay`, `pip_stream_layout`, `food_overhead`, `dance_full_body`, `outfit_showcase`, `clip_compilation`, `live_performance`, `other`

### visual_complexity

`minimal_clean`, `moderate`, `busy`

### caption_style

`standard_subtitles`, `animated_word_by_word`, `large_bold_centered`, `karaoke_highlight`, `meme_top_bottom`

### camera_perspective

`selfie_front`, `rear_camera`, `tripod_static`, `handheld_moving`, `overhead_topdown`, `drone_aerial`

### setting

`indoor_home_general`, `indoor_bedroom`, `indoor_bathroom`, `indoor_kitchen`, `indoor_living_room`, `indoor_studio`, `indoor_gym`, `indoor_office`, `indoor_classroom`, `outdoor_urban`, `outdoor_nature`, `outdoor_beach`, `outdoor_pool`, `car`, `vehicle_other`, `restaurant_cafe`, `store_retail`, `event_venue`, `studio_set`, `indoor_salon_spa`, `indoor_medical_clinic`, `indoor_workshop_garage`, `outdoor_rural_farm`, `sports_venue`, `stage_performance`, `construction_site`, `place_of_worship`, `generic`

### lighting_quality

`professional`, `cinematic`, `natural_good`, `golden_hour`, `natural_dim`, `ring_light`, `harsh_artificial`, `mixed`, `low_quality`

### visual_hook_type

`text_hook`, `extreme_closeup`, `before_state`, `shocking_image`, `aesthetic_setup`, `person_speaking_to_camera`, `motion_action`, `crowded_scene`, `mystery_object`, `dramatic_zoom`, `animal_pet`, `none`

### text_overlay_purpose

`title`, `list_item`, `statistic`, `quote`, `dialogue_label`, `chapter_marker`, `branding`, `cta`, `meme_caption`, `none`

### narrative_arc (slideshows)

`listicle`, `story_progression`, `before_after`, `comparison`, `escalating_reveal`, `single_idea_expanded`, `q_and_a`, `meme_setup_punchline`, `tutorial_steps`, `screenshot_dump`, `none`

### text_density (slideshows)

`text_dominant`, `balanced`, `image_dominant`

### people_source

`visual` (from frames), `text` (guessed from the words when no frames were usable), `none`

### people_count_bucket

`none`, `one`, `two`, `small_group_3_5`, `large_group_6_plus`, `crowd`

### on_screen_presence

`face_presenting`, `face_not_presenting`, `body_no_face`, `hands_or_pov_only`, `animated_or_avatar_character`, `no_person`

### presence_style

`on_camera_presenter`, `on_camera_subject`, `faceless_voiceover`, `faceless_silent`, `hands_or_pov`, `avatar_or_animated`, `no_person`

### apparent_gender

Used by `people[].apparent_gender`, `primary_subject_gender` and `genders_present`: `female`, `male`, `ambiguous`. `ambiguous` means the AI looked and could not tell.

### subject_gender_skew

`all_female`, `all_male`, `mixed`, `ambiguous`, `none`

### apparent_age_bracket

Used by `people[].apparent_age_bracket`, `primary_subject_age_bracket` and `age_brackets_present`: `infant_0_3`, `child_4_12`, `teen_13_17`, `adult_18_24`, `adult_25_34`, `adult_35_49`, `adult_50_64`, `senior_65_plus`, `unknown`

### person role and confidence

`people[].role`: `primary`, `secondary`, `background`

`people[].confidence`, `primary_subject_confidence` and `ai_confidence`: `high`, `medium`, `low`

### ai_provenance

`none`, `ai_assisted`, `ai_voice`, `ai_visuals`, `ai_presenter`, `fully_ai`, `unknown`

From least to most AI-made. `ai_assisted`: AI only helped edit or caption. `ai_voice`: real footage, AI voice. `ai_visuals`: AI-made images or video, no AI presenter. `ai_presenter`: an AI avatar presents. `fully_ai`: footage, voice and presenter are all AI.

### ai_elements

`synthetic_presenter`, `generated_imagery`, `generated_video`, `face_swap_or_clone`, `synthetic_voice`, `ai_captions_or_edit`

### product_presence

`branded_product_featured`, `unbranded_product_featured`, `product_incidental`, `no_product`

To find no-name products, use `unbranded_product_featured`. Empty brand lists can't tell you that.

### animals_visible

`dog`, `cat`, `other_pet`, `bird`, `horse`, `livestock`, `wildlife`, `aquatic`, `other`

### animal_presence

`animal_featured` (the video is about an animal), `animal_incidental`, `none`

### screen_content_type

`gameplay`, `text_conversation`, `notes_or_document`, `social_feed`, `app_ui`, `website`, `video_playback`, `other`

---

## Full example

A complete video from [Get videos](https://dev.virlo.ai/docs/agents#get-agent-videos) on an agent with Data Intelligence on. `publish_date` has no time zone marker. Read it as UTC.

**Show the full video (JSON)**

**Video with intelligence:**

```json
{
  "id": "b3a1f892-7c4e-4d8a-9f12-6e8b4a2c1d05",
  "url": "https://www.tiktok.com/@glowbysara/video/7392847561023456789",
  "description": "the glass skin routine that changed my life, step by step for beginners #skincare #glassskin #routine",
  "platform": "tiktok",
  "views": 2847000,
  "likes": 341200,
  "shares": 89400,
  "comments": 12800,
  "bookmarks": 267000,
  "publish_date": "2026-04-22T14:30:00",
  "author": {
    "country": "US",
    "username": "glowbysara",
    "verified": true,
    "followers": 890000,
    "avatar_url": "https://auth.virlo.ai/storage/v1/object/public/avatars/example.jpg"
  },
  "hashtags": ["skincare", "glassskin", "routine"],
  "thumbnail_url": "https://auth.virlo.ai/storage/v1/object/public/thumbnails/example.webp",
  "keyword_found_by": "glass skin routine",
  "is_duet": false,
  "is_stitch": false,
  "upload_region": "US",
  "upload_region_source": "tiktok_region",
  "intelligence": {
    "primary_topic": "beginner glass skin routine",
    "secondary_topics": ["glass skin", "drugstore skincare", "beginner skincare"],
    "keywords": ["glass skin", "double cleanse", "hyaluronic acid", "niacinamide", "SPF"],
    "category": "beauty",
    "content_format": "tutorial",
    "visual_format": "product_closeup",
    "visual_complexity": "moderate",
    "camera_perspective": "tripod_static",
    "setting": "indoor_bathroom",
    "lighting_quality": "ring_light",
    "has_face_visible": true,
    "scene_changed": true,
    "background_type": "real_world_photo",
    "background_reasoning": "The same bathroom vanity is behind the creator in every shot.",
    "foreground_type": "real_photo_subject",
    "foreground_reasoning": "The creator and the skincare products are the focus.",
    "hook_text": "the glass skin routine that changed my life",
    "hook_type": "bold_claim",
    "visual_hook_type": "before_state",
    "has_text_overlay": true,
    "text_overlay_purpose": "list_item",
    "text_overlay_content": "Step 1: double cleanse",
    "has_onscreen_captions": true,
    "caption_style": "animated_word_by_word",
    "transcript_quality": "clean",
    "transcript_word_count": 312,
    "transcript_character_count": 1680,
    "language_detected": "en",
    "is_multilingual": false,
    "emotional_tone": "inspiring",
    "sentiment": "positive",
    "speaking_style": "conversational",
    "brand_safety_tier": "safe",
    "is_nsfw": false,
    "sensitive_topics": ["none"],
    "is_sponsored": false,
    "brands_mentioned": ["CeraVe", "The Ordinary"],
    "cta_usages": [
      { "type": "follow", "text": "follow for part 2" },
      { "type": "save_post", "text": "save this routine" }
    ],
    "social_proof_used": ["before_after_results", "popularity_claim"],
    "trend_references": ["glass skin"],
    "summary": "Step-by-step beginner glass skin routine using drugstore products, from double cleansing to SPF. The creator talks to the camera in a bathroom and shows before and after results.",
    "is_educational": true,
    "low_confidence_fields": [],
    "people": [
      {
        "role": "primary",
        "is_synthetic": false,
        "apparent_gender": "female",
        "apparent_age_bracket": "adult_18_24",
        "apparent_age_min": 20,
        "apparent_age_max": 26,
        "confidence": "high"
      }
    ],
    "people_source": "visual",
    "people_count_bucket": "one",
    "on_screen_presence": "face_presenting",
    "presence_style": "on_camera_presenter",
    "primary_subject_gender": "female",
    "primary_subject_age_bracket": "adult_18_24",
    "primary_subject_age_min": 20,
    "primary_subject_age_max": 26,
    "primary_subject_confidence": "high",
    "genders_present": ["female"],
    "age_brackets_present": ["adult_18_24"],
    "subject_gender_skew": "all_female",
    "has_minor_visible": false,
    "has_real_person": true,
    "has_baby_visible": false,
    "has_senior_visible": false,
    "ai_provenance": "none",
    "ai_elements": [],
    "ai_confidence": "high",
    "ai_reasoning": "Real creator speaking to camera. No AI-made imagery, voice or presenter detected.",
    "brands_visible": ["CeraVe", "The Ordinary"],
    "product_presence": "branded_product_featured",
    "ip_characters": [],
    "animals_visible": [],
    "animal_presence": "none",
    "has_animal": false,
    "screen_content_type": null,
    "is_silent": false,
    "is_compilation": false,
    "is_ranking": false,
    "is_repost": false,
    "is_before_after": true,
    "is_layered_composition": false,
    "has_platform_watermark": false,
    "watermark_source": null
  },
  "intent_match": {
    "matches": true,
    "reasoning": "The caption and transcript walk through a beginner glass skin routine step by step and name drugstore products, which fits the intent."
  },
  "sound": null,
  "intelligence_status": "ready"
}
```

---

More in Content Research Agents:

- [Overview](https://dev.virlo.ai/docs/agents.md)
- [Autopilot](https://dev.virlo.ai/docs/agents/autopilot.md)
- [Intent cookbook](https://dev.virlo.ai/docs/intent-cookbook.md)
