Data Intelligence

Optional per-item enrichment: 43 structured fields on every video in a Content Research Agent run (parallel schema for slideshows) — topics, hooks, visual format, tone, brand safety, CTAs, and more.


Overview

When data_intelligence_enabled is true, each collected video gets an intelligence object for filtering and aggregation. Fields populate asynchronously (typically 30–90s after collection) — see Async Data Model for intelligence_status.


How to enable

Set data_intelligence_enabled: true on POST /v1/agents (see Content Research Agents). Legacy Orbit/Comet accept the same flag until August 3, 2026.

Enable on an Agent

POST
/v1/agents
curl -X POST https://api.virlo.ai/v1/agents \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Skincare Routine Research",
    "is_recurring": false,
    "intent": "Find viral skincare routines",
    "keywords": ["skincare routine", "glass skin tutorial"],
    "platforms": ["tiktok", "youtube"],
    "data_intelligence_enabled": true
  }'

Pricing

Data Intelligence adds $1.00 per run on top of the base search cost.

ProductBase cost+ IntelligenceTotal
Agent run (one-shot)$0.50+$1.00$1.50
Agent run (recurring / scheduled)$0.50+$1.00$1.50 per cycle

Field reference

All intelligence fields are returned inside the intelligence object on each video. Fields are grouped into categories below.

Content Classification

FieldTypeDescription
primary_topicstringMain topic/subject of the video
secondary_topicsstring[]Additional topics covered
keywordsstring[]Extracted keywords relevant to the content
categorystringHigh-level content category (open vocabulary)
content_formatstringContent format type (open vocabulary)

Visual Analysis

FieldTypeDescription
visual_formatenumPrimary visual presentation style
visual_complexityenumHow visually busy/complex the video is
camera_perspectiveenumCamera angle and positioning
settingenumPhysical location/environment
lighting_qualityenumLighting quality assessment
has_face_visiblebooleanWhether a human face is visible
scene_changedbooleanWhether the scene changes over the course of the video
background_typeenumClassification of the video's background
background_reasoningstringShort rationale for the background_type classification
foreground_typeenumClassification of the video's foreground subject
foreground_reasoningstringShort rationale for the foreground_type classification

Hook & Copy

FieldTypeDescription
hook_textstringThe opening hook text/spoken words
hook_typeenumCategory of hook technique used
visual_hook_typeenumVisual hook technique in the first frames
has_text_overlaybooleanWhether text overlays are present
text_overlay_purposeenumPurpose of the primary text overlay
text_overlay_contentstringTranscribed text of the primary on-screen text overlay

Captions & Transcript

FieldTypeDescription
has_onscreen_captionsbooleanWhether burned-in captions are shown
caption_styleenumVisual style of on-screen captions
transcript_qualityenumTranscript quality rating
transcript_word_countintegerTotal words in the transcript
transcript_character_countintegerTotal characters in the transcript
language_detectedstringISO 639-1 language code
is_multilingualbooleanWhether more than one language is spoken

Tone & Sentiment

FieldTypeDescription
emotional_toneenumDominant emotional tone of the content
sentimentenumOverall sentiment polarity
speaking_styleenumVocal delivery style

Brand & Safety

FieldTypeDescription
brand_safety_tierenumBrand safety classification tier
is_nsfwbooleanWhether content is not safe for work
sensitive_topicsenum[]List of sensitive topics detected
is_sponsoredbooleanWhether the video is sponsored content
brands_mentionedstring[]Brand names mentioned in the video

Engagement Signals

FieldTypeDescription
cta_usagesobject[]Calls to action used — each entry is { type, text } where type is one of the cta enum values and text is the verbatim CTA wording
social_proof_usedenum[]Social proof techniques employed
trend_referencesstring[]References to current trends or challenges

Meta

FieldTypeDescription
summarystringOne-sentence summary of the video content
is_educationalbooleanWhether the video is primarily educational
low_confidence_fieldsstring[]Field names where the model had low confidence

Slideshow intelligence

Slideshows (TikTok image carousels and equivalent on other platforms) are analysed by a separate intelligence pipeline that produces a parallel — but not identical — set of fields on each slideshow item. The lifecycle (pendingready) and the intelligence_status enum are identical to videos, so a single envelope-aware client handles both. The schema differences are summarised below.

Same fields as videos

The following fields are produced for both videos and slideshows with identical semantics:

primary_topic · secondary_topics · keywords · category · content_format · background_reasoning · background_type · foreground_reasoning · foreground_type · hook_text · hook_type · language_detected · is_multilingual · emotional_tone · sentiment · has_face_visible · setting · brand_safety_tier · is_nsfw · sensitive_topics · is_educational · is_sponsored · brands_mentioned · cta_usages · trend_references · social_proof_used · summary · low_confidence_fields

Slideshow-only fields (not present on videos)

FieldTypeDescription
narrative_arcenumHow the panels are sequenced (e.g. problem_solution, buildup_to_reveal, numbered_list, linear_storytelling).
text_densityenumHow much copy is on the panels overall (minimal · moderate · high).
image_countintegerNumber of panels in the slideshow.
panel_textsstring[]Per-panel transcribed text, indexed in order.
panel_text_fullstringAll panel text concatenated — the "transcript equivalent" for slideshows.
panel_text_word_countintegerWord count across panel_text_full.
panel_text_character_countintegerCharacter count across panel_text_full.

Video-only fields (not present on slideshows)

Slideshows have no spoken audio, no continuous motion, and no per-frame camera work — so the following video-only fields are omitted from the slideshow schema:

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

Example slideshow intelligence

{
  "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": "numbered_list",
    "text_density": "moderate",
    "image_count": 4,
    "panel_texts": [
      "Step 1: gentle cleanser",
      "Step 2: niacinamide serum",
      "Step 3: lightweight moisturiser",
      "Step 4: SPF 50"
    ],
    "panel_text_full": "Step 1: gentle cleanser. Step 2: niacinamide serum. Step 3: lightweight moisturiser. Step 4: SPF 50",
    "panel_text_word_count": 15,
    "panel_text_character_count": 102,
    "language_detected": "en",
    "is_multilingual": false,
    "emotional_tone": "inspiring",
    "sentiment": "positive",
    "has_face_visible": false,
    "background_type": "real_world_photo",
    "foreground_type": "real_photo_subject",
    "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": [],
    "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": []
  }
}

Enum values

Below are the canonical allowed values for each enum field. Values are stable — new additions are append-only and versioned.

background_type

solid_color · illustrated_scene · real_world_photo · digital_screen_capture

A phone or laptop sitting on a real-world surface is real_world_photo — only direct in-app screenshots (no real-world camera involved) 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

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

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 · other

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 · 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


Full example

Below is a complete video object with intelligence fields populated — a viral skincare routine video on TikTok.

Intelligence-enriched video response

{
  "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:00Z",
  "author": {
    "username": "glowbysara",
    "avatar_url": "https://p16-sign-sg.tiktokcdn.com/avatar/glowbysara.jpg",
    "followers": 890000,
    "verified": true
  },
  "hashtags": ["skincare", "glassskin", "routine", "skincareroutine", "glowup"],
  "keyword_found_by": "glass skin tutorial",
  "intelligence": {
    "primary_topic": "skincare routine",
    "secondary_topics": ["glass skin", "Korean beauty", "beginner skincare"],
    "keywords": ["glass skin", "double cleanse", "hyaluronic acid", "snail mucin", "SPF"],
    "category": "beauty_skincare",
    "content_format": "step_by_step_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": "Consistent bathroom vanity backdrop across shots.",
    "foreground_type": "real_photo_subject",
    "foreground_reasoning": "Creator and skincare products are the focal subjects.",
    "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": ["COSRX", "Laneige", "Beauty of Joseon"],
    "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 challenge", "75 hard skincare"],
    "summary": "Step-by-step Korean glass skin routine for beginners using drugstore and K-beauty products, demonstrating double cleansing through SPF application with before/after results.",
    "is_educational": true,
    "low_confidence_fields": []
  },
  "intent_match": {
    "matches": true,
    "reasoning": "Video features a step-by-step tutorial format (content_format: step_by_step_tutorial) with product closeups (visual_format: product_closeup), on-screen captions (has_onscreen_captions: true), and a positive, educational tone — all consistent with the intent to find beginner-friendly skincare routines."
  }
}

Use cases

Brand safety filtering

Filter out high-risk content before syndicating UGC or placing ads. Query videos and filter on brand_safety_tier and sensitive_topics to enforce your brand guidelines programmatically.

safe_videos = [
    v for v in videos
    if v['intelligence']['brand_safety_tier'] in ('safe', 'low_risk')
    and 'none' in v['intelligence']['sensitive_topics']
]

Hook analysis at scale

Analyze which hook types drive the highest engagement across your niche. Aggregate hook_type and visual_hook_type fields to discover patterns in top-performing content.

from collections import Counter

hook_stats = Counter(v['intelligence']['hook_type'] for v in videos)
top_hooks = hook_stats.most_common(5)

Content format breakdown

Understand the visual format distribution in a niche to inform your production style. Group by visual_format and compare average view counts per format.

from statistics import mean

format_views = {}
for v in videos:
    fmt = v['intelligence']['visual_format']
    format_views.setdefault(fmt, []).append(v['views'])

format_avg = {fmt: mean(views) for fmt, views in format_views.items()}

Competitive intelligence

Track which brands are mentioned in viral content, identify sponsorship patterns, and monitor competitor share-of-voice by aggregating brands_mentioned and is_sponsored across large video sets.

const brandMentions = {};
videos.forEach(v => {
  v.intelligence.brands_mentioned.forEach(brand => {
    brandMentions[brand] = (brandMentions[brand] || 0) + 1;
  });
});

const sorted = Object.entries(brandMentions)
  .sort(([,a], [,b]) => b - a);

Intent-based format discovery

Use intent with Data Intelligence to find videos matching a specific content format. For example, find "long-text-overlay reaction" videos — a person on camera with long overlay text (>50 words), not speaking, reacting to the text.

import requests

response = requests.post(
    'https://api.virlo.ai/v1/agents',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    json={
        'name': 'Long Text Overlay Reactions',
        'is_recurring': False,
        'keywords': ['reaction', 'storytime', 'text overlay'],
        'platforms': ['tiktok'],
        'data_intelligence_enabled': True,
        'intent': 'Find videos where a person is visible on camera with long text overlay (>50 words), not speaking, reacting to the overlay text'
    }
)

# After the run completes (finalized=true), filter to matched videos only
agent_id = response.json()['data']['id']
matched = requests.get(
    f'https://api.virlo.ai/v1/agents/{agent_id}/videos',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    params={'intent_match': 'true'}
)

Workflow

This section walks through common end-to-end workflows that combine Data Intelligence with intent matching. Each example shows the full lifecycle: creating the search, waiting for results, and consuming the enriched data.

Example 1: Find UGC-style talking-head product reviews

A brand team wants to find authentic-feeling product reviews in the talking-head format for a UGC campaign.

Step 1 — Create an agent with intent and intelligence

import requests, time

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,
    'keywords': ['product review', 'honest review', 'trying this product'],
    'platforms': ['tiktok', 'instagram'],
    'data_intelligence_enabled': True,
    'intent': (
        'Find talking-head style product reviews where the creator '
        'is speaking to camera, showing the product, with a conversational '
        'or instructional tone. Must not be a sponsored post.'
    ),
}).json()

agent_id = agent['data']['id']
print(f'Agent created: {agent_id}')

Step 2 — Poll until finalized

while True:
    result = requests.get(
        f'{API}/agents/{agent_id}',
        headers=HEADERS,
    ).json()

    if result['data'].get('finalized') is True:
        break
    time.sleep(30)

# Match counts via the intent_match filter — `total` is the grand total,
# so limit=1 is enough to read the counts cheaply (reads are free).
matched_total = requests.get(
    f'{API}/agents/{agent_id}/videos',
    headers=HEADERS,
    params={'intent_match': 'true', 'limit': 1},
).json()['data']['total']
overall_total = requests.get(
    f'{API}/agents/{agent_id}/videos',
    headers=HEADERS,
    params={'limit': 1},
).json()['data']['total']
print(f'Matched: {matched_total} / {overall_total}')

Step 3 — Fetch only matching videos

matched = requests.get(
    f'{API}/agents/{agent_id}/videos',
    headers=HEADERS,
    params={'intent_match': 'true'},
).json()

for video in matched['data']['videos']:
    intel = video['intelligence']
    print(f"{video['url']}")
    print(f"  Format: {intel['visual_format']}")
    print(f"  Tone: {intel['emotional_tone']}")
    print(f"  Sponsored: {intel['is_sponsored']}")
    print(f"  Match reason: {video['intent_match']['reasoning']}")
    print()

Example 2: Brand safety audit with a recurring agent

A media buyer sets up a daily agent to continuously monitor a niche and flag any content that might be unsafe for ad placement.

Step 1 — Create the recurring agent

agent = requests.post(f'{API}/agents', headers=HEADERS, json={
    'name': 'Daily Brand Safety Audit - Fitness',
    'is_recurring': True,
    'cadence': 'daily',
    'keywords': ['fitness transformation', 'weight loss journey'],
    'platforms': ['tiktok', 'youtube'],
    'data_intelligence_enabled': True,
    'intent': (
        'Find fitness content that is brand-safe for health supplement ads. '
        'Must NOT contain: medical claims, eating disorder references, '
        'extreme dieting, strong profanity, or NSFW content.'
    ),
}).json()

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

Step 2 — After each cycle completes, fetch brand-safe matches

Use the content_research_agent.run.completed webhook or poll the agent videos endpoint.

safe_videos = requests.get(
    f'{API}/agents/{agent_id}/videos',
    headers=HEADERS,
    params={'intent_match': 'true'},
).json()

for v in safe_videos['data']['videos']:
    intel = v['intelligence']
    print(f"{v['url']}")
    print(f"  Safety: {intel['brand_safety_tier']}")
    print(f"  Sensitive: {intel['sensitive_topics']}")
    print(f"  Reason: {v['intent_match']['reasoning']}")

Step 3 — Get the match counts

# `total` on the filtered list is the grand total (Shape A), so
# limit=1 reads each count in one cheap, free call.
def count(params):
    return requests.get(
        f'{API}/agents/{agent_id}/videos',
        headers=HEADERS,
        params={**params, 'limit': 1},
    ).json()['data']['total']

print(f"Brand-safe: {count({'intent_match': 'true'})}")
print(f"Flagged: {count({'intent_match': 'false'})}")

Example 3: Webhook-driven pipeline

For production systems, use webhooks instead of polling. When an agent run completes, the webhook payload includes intent_summary with matched_urls for immediate processing.

from flask import Flask, request

app = Flask(__name__)

@app.post('/webhooks/virlo')
def handle_webhook():
    payload = request.json
    event = payload['event']

    if event == 'content_research_agent.run.completed':
        data = payload['data']
        summary = data.get('intent_summary')

        if summary and summary['matched'] > 0:
            # Process matched URLs immediately — no pagination needed
            for url in summary['matched_urls']:
                enqueue_for_review(url)

            print(f"Enqueued {summary['matched']} matched videos for review")

    return '', 200

Intent matching

When you provide an intent along with data_intelligence_enabled: true, the system performs a post-intelligence evaluation step. After all 43 intelligence fields are populated for each video, the system uses those structured fields to evaluate whether each video matches your stated intent.

How it works

  1. You create an agent with both intent and data_intelligence_enabled: true
  2. Videos are collected and intelligence fields are populated as usual
  3. Each video's intelligence fields are evaluated against your intent using Gemini Flash
  4. Results are stored per-run and returned on video objects

The intent_match object

Each video object receives an intent_match field as a sibling of intelligence (not nested inside it):

{
  "id": "b3a1f892-...",
  "url": "https://www.tiktok.com/@creator/video/123",
  "views": 2847000,
  "intelligence": { ... },
  "intent_match": {
    "matches": true,
    "reasoning": "Video has a visible face (has_face_visible: true), long text overlay with >50 words (transcript_word_count: 312, has_text_overlay: true), and conversational speaking style consistent with reaction format."
  }
}
FieldTypeDescription
matchesbooleanWhether the video matches the stated intent based on its intelligence fields
reasoningstringExplanation citing specific intelligence field values that informed the decision

intent_match is null when:

  • intent was not provided on the agent
  • data_intelligence_enabled is false
  • Intelligence analysis failed for the video

The intent_summary object

Aggregate match statistics are delivered as an intent_summary object in the content_research_agent.run.completed webhook payload (and, during the migration window, on the legacy GET /v1/orbit/:orbit_id status response). GET /v1/agents/:id does not currently return it — on the unified surface, read match counts via the intent_match filter on the videos endpoint (total is the grand total), or consume the webhook:

{
  "intent_summary": {
    "matched": 28,
    "total_evaluated": 142,
    "not_evaluated": 5,
    "matched_urls": [
      "https://www.tiktok.com/@creator1/video/123",
      "https://www.tiktok.com/@creator2/video/456"
    ]
  }
}
FieldTypeDescription
matchedintegerNumber of videos that matched the intent
total_evaluatedintegerTotal videos evaluated against the intent
not_evaluatedintegerVideos skipped (e.g. intelligence failed)
matched_urlsstring[]URLs of all matched videos

intent_summary is null when the run did not use both intent and data_intelligence_enabled: true.

Filtering by intent match

Use the ?intent_match query parameter on paginated video endpoints to filter server-side:

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

The deprecated /v1/orbit/:orbit_id/videos and /v1/comet/:id/videos paths accept the same filter until their removal on August 3, 2026.

This returns only videos where intent_match.matches equals the specified value, avoiding the need to fetch all videos and filter client-side.

Was this page helpful?