Analyse a video
/v1/videos/{platform}/{id}/intelligenceThe full video pass: a scene pass over the media, then a structured pass producing one versioned object with content, hook, persuasion, tone, visual and safety groups. TikTok, Instagram, YouTube (Shorts and long-form) and Facebook. Cached forever per version. Videos with no speech still run; only unfetchable media (a post that is gone, private or restricted, or media the pass cannot read) fails, with media_unavailable, which is terminal: retrying the same video will not succeed. A video longer than 10 minutes is refused with 422 video_too_long: before any Run when its length is known (a YouTube video's length is asked of YouTube first, free), else the Run fails uncharged.
Open in Playground(no API Key needed)
Price
23 credits, whether computed now or already stored, and with refresh: true.
Supports estimate=true: the reply is the price in Credits and whether it would be cached, with no Run created and nothing charged.
A cached result costs the same as a fresh one; cached says which you got.
Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
platform | path | stringone of tiktok, instagram, youtube, facebook | Required | — | Platform the subject lives on. |
id | path | string | Required | — | Platform video id. |
wait | query | integermin 0; max 120 | Optional | 30 | Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; given in both places with different values it is a 400. |
estimate | query | boolean | Optional | — | true returns { estimate_credits, cached } computed from what is already stored, and creates no Run. Nothing is charged. When a source the call would read is unavailable and the answer is not served from cache, the estimate adds unavailable, the { platform, capability } parts it cannot reach, or answers 503 source_unavailable when it can reach none. Only true or false; anything else is a 400. A POST may send it in the body instead; given in both places with different values it is a 400. |
Idempotency-Key | header | string | Optional | — | 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 idempotency_conflict. |
Request body
application/json
object
refreshbooleanRecompute even when a current version is stored: the scene pass over the media runs again, not just the structured pass. Always charged as fresh.
Responses
200 OK
The Run finished inside the wait budget: { run, result }. With estimate=true, { estimate_credits, cached } instead.
any of 2 shapes
Option 1object
runobjectrequiredEvery charged operation creates a Run. The envelope is identical whether the result came back inline or was polled.idstringrequiredkindstringrequiredWhat was run, e.g.video_lookup.statusstringrequiredone ofrunning,completed,partial,failedpartialkeeps the completed items and charges for them, up to what the Balance covers;failedcharges nothing.created_atstringrequiredRFC 3339.completed_atstringrequirednullableNull while the Run is still running.charge_creditsintegerrequiredWhat this Run actually cost, in Credits. A stored answer costs the same as a fresh one.list_price_creditsintegerrequiredThe full price of the work asked for, in Credits. Equal tocharge_creditsunless the Run charged for only part of it.cachedbooleanrequiredTrue when a stored result answered without paying the vendor.progressobjectWhat a running Research has done so far, refreshed as it works:stage(keywords,searching,filtering,analysing,writing_digest) and the counts known by then —keywords,found,kept,dropped_off_topic, andanalysedofshortlisted. A count is absent until its stage produced it. Absent once the Run settles, and on every other kind.stagestringrequiredone ofkeywords,searching,filtering,analysing,writing_digestkeywordsintegervalue at least 0searchesintegervalue at least 0foundintegervalue at least 0judgedintegervalue at least 0keptintegervalue at least 0dropped_off_topicintegervalue at least 0dropped_unsureintegervalue at least 0shortlistedintegervalue at least 0analysedintegervalue at least 0updated_atstringrequired
resultobjectrequiredOne normalised video, the same shape on every platform.platformstringrequiredidstringrequiredurlstringrequirednullabledescriptionstringrequiredposted_atstringrequirednullableduration_secondsnumberrequirednullablethumbnail_urlstringrequirednullablesoundobjectrequirednullableidstringrequiredtitlestringrequirednullable
creatorobjectrequirednullableidstringrequiredhandlestringrequirednullablenamestringrequiredfollower_countnumberrequirednullable
snapshotobjectrequirednullableatstringrequiredviewsnumberrequiredlikesnumberrequiredcommentsnumberrequiredsavesnumberrequired
scoresobjectrequirednullableNull when the creator has no baseline yet.outliernumberrequirednullableViews over the creator's median views.outlier_p90numberrequirednullablepercentilenumberrequirednullableWhere this video sits in the creator's own sample, 0-100.velocityobjectrequirednullableviews_per_hournumberrequiredwindowstringrequired
reasonstringrequirednullablealwaysaged_outaged_outwhen the video is over 30 days old and Scores are null.baselineobjectrequiredThe yardstick every Score on this creator is measured against.median_viewsnumberrequiredp90_viewsnumberrequiredengagement_ratenumberrequirednullableposts_per_weeknumberrequirednullablesampleintegerrequiredconfidencestringrequiredone ofhigh,lowsourcestringrequiredone ofvideos,follower_tiercomputed_atstringrequirednullablestalebooleanrequiredTrue past 7 days; the next lookup refreshes it.
coveragestringalwayspartialPresent only where the platform's index is best-effort.intelligenceobjectrequiredThe versioned analysis of one video: what it says, how it opens, what it sells, how it sounds, how it looks and whether it is brand safe.versionintegerrequiredmodelstringrequiredcomputed_atstringrequiredsourcestringrequiredcontentobjectrequiredsummarystringrequirednichestringrequiredlanguagesarray of stringrequiredcontent_stylesarray of stringrequiredkeywordsarray of objectrequiredkeywordstringrequiredcategorystringrequiredsentimentnumberrequired
transcriptstringrequirednullable
hookobjectrequiredopening_linestringrequirednullabletypestringrequiredvisual_hookstringrequirednullablerationalestringrequirednullable
persuasionobjectrequiredcall_to_actionobjectrequiredtypestringrequiredtextstringrequirednullable
sponsoredbooleanrequiredsponsorship_signalsarray of stringrequiredbrands_and_productsarray of objectrequirednamestringrequiredkindstringrequiredprominencestringrequired
trend_referencesarray of objectrequirednamestringrequiredkindstringrequired
toneobjectrequiredsentiment_labelstringrequiredsentiment_scorenumberrequiredprimary_emotionstringrequiredemotion_intensitynumberrequirednullableemotion_rationalestringrequirednullablespeaking_stylestringrequiredspeaking_style_notesstringrequirednullable
visualobjectrequiredformatstringrequiredsettingstringrequiredface_visibilitystringrequiredon_screen_textarray of stringrequiredhas_on_screen_captionsbooleanrequiredcamera_notesstringrequirednullablelighting_notesstringrequirednullableelementsany
safetyobjectrequiredbrand_safety_tierstringrequiredreasonsarray of stringrequirednotesstringrequirednullable
Option 2objectReturned instead of a Run when
estimate=true. Nothing is charged. Operations with more to say about a quote add members; the two above are always present.estimate_creditsintegerrequiredWhat the call would be charged, in Credits.cachedbooleanrequiredWhether a stored result can already answer. It does not change the price.
200 · The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_credits, cached }` instead.
{
"run": {
"id": "run_8f2c1ad04b",
"kind": "video_intelligence",
"status": "completed",
"created_at": "2026-09-08T09:14:02.418Z",
"completed_at": "2026-09-08T09:14:04.930Z",
"charge_credits": 23,
"list_price_credits": 23,
"cached": false
},
"result": {
"platform": "tiktok",
"id": "7300000000000000000",
"url": "https://www.tiktok.com/@someone/video/7300000000000000000",
"description": "3 minutes at 4°C every morning for 30 days. Here is what actually changed. #coldplunge #recovery",
"posted_at": "2026-09-05T06:32:11.000Z",
"duration_seconds": 41,
"thumbnail_url": "https://p16-sign.tiktokcdn.com/obj/tos-cover-7300000000000000000.jpeg",
"hashtags": [
"coldplunge",
"recovery",
"morningroutine"
],
"sound": {
"id": "7180000000000000000",
"title": "original sound - someone"
},
"creator": {
"id": "cre_9d4b2a7f1c",
"handle": "someone",
"name": "Someone",
"follower_count": 184300
},
"snapshot": {
"at": "2026-09-08T09:14:04.219Z",
"views": 1284000,
"likes": 143200,
"comments": 2810,
"shares": 9040,
"saves": 21600
},
"scores": {
"outlier": 4.21,
"outlier_p90": 1.6,
"percentile": 95,
"velocity": {
"views_per_hour": 6698.6,
"window": "snapshot"
},
"reason": null,
"baseline": {
"median_views": 305000,
"p90_views": 802000,
"engagement_rate": 0.1342,
"posts_per_week": 4.1,
"sample": 20,
"confidence": "high",
"source": "videos",
"computed_at": "2026-09-08T09:14:03.902Z",
"stale": false
}
},
"intelligence": {
"version": 1,
"model": "gemini-2.5-flash",
"computed_at": "2026-09-08T09:14:04.612Z",
"source": "speech",
"content": {
"summary": "A 30-day cold plunge diary that leads with the measurable change and keeps the discomfort visible rather than heroic.",
"niche": "HEALTH_AND_FITNESS",
"languages": [
"en"
],
"content_styles": [
"TALKING_HEAD",
"PROGRESS_DIARY"
],
"keywords": [
{
"keyword": "cold plunge",
"category": "ACTION",
"sentiment": 0.4
},
{
"keyword": "recovery",
"category": "TOPIC",
"sentiment": 0.6
}
],
"transcript": "Day one I lasted eleven seconds. Day thirty I stopped noticing the cold at all, and that is the part nobody warns you about..."
},
"hook": {
"opening_line": "Day one I lasted eleven seconds.",
"type": "SPECIFIC_DETAIL",
"visual_hook": "Timer overlay counting up on a shot of the plunge tub at dawn",
"rationale": "A concrete, unimpressive number invites the viewer to stay for the number it becomes."
},
"persuasion": {
"call_to_action": {
"type": "FOLLOW",
"text": "Following along? Day 60 starts Monday."
},
"sponsored": false,
"sponsorship_signals": [],
"brands_and_products": [
{
"name": "Plunge",
"kind": "PRODUCT",
"prominence": "INCIDENTAL"
}
],
"trend_references": [
{
"name": "30-day challenge",
"kind": "FORMAT"
}
]
},
"tone": {
"sentiment_label": "Positive",
"sentiment_score": 0.42,
"primary_emotion": "Determination",
"emotion_intensity": 0.6,
"emotion_rationale": "The delivery stays level through visible discomfort, which reads as resolve rather than excitement.",
"speaking_style": "CONVERSATIONAL",
"speaking_style_notes": "Unhurried voiceover over hard cuts between mornings, with no music bed under the first ten seconds."
},
"visual": {
"format": "VLOG",
"setting": "OUTDOOR_HOME",
"face_visibility": "CREATOR_ON_CAMERA",
"on_screen_text": [
"DAY 1",
"DAY 30",
"11s → 3:00"
],
"has_on_screen_captions": true,
"camera_notes": "Fixed tripod at tub height, one handheld reaction shot per morning, cut roughly every two seconds.",
"lighting_notes": "Natural dawn light, cool and slightly underexposed throughout.",
"elements": [
{
"type": "OBJECT",
"name": "ice bath",
"confidence": 0.97
}
]
},
"safety": {
"brand_safety_tier": "SAFE",
"reasons": [],
"notes": null
}
}
}
}200 · With `estimate=true`: the price, no Run, no charge.
{
"estimate_credits": 23,
"cached": false
}202 Accepted
Still running: { run }. Poll GET /v1/runs/{id}.
object
Still running. Poll GET /v1/runs/{id}.
runobjectrequiredEvery charged operation creates a Run. The envelope is identical whether the result came back inline or was polled.idstringrequiredkindstringrequiredWhat was run, e.g.video_lookup.statusstringrequiredone ofrunning,completed,partial,failedpartialkeeps the completed items and charges for them, up to what the Balance covers;failedcharges nothing.created_atstringrequiredRFC 3339.completed_atstringrequirednullableNull while the Run is still running.charge_creditsintegerrequiredWhat this Run actually cost, in Credits. A stored answer costs the same as a fresh one.list_price_creditsintegerrequiredThe full price of the work asked for, in Credits. Equal tocharge_creditsunless the Run charged for only part of it.cachedbooleanrequiredTrue when a stored result answered without paying the vendor.progressobjectWhat a running Research has done so far, refreshed as it works:stage(keywords,searching,filtering,analysing,writing_digest) and the counts known by then —keywords,found,kept,dropped_off_topic, andanalysedofshortlisted. A count is absent until its stage produced it. Absent once the Run settles, and on every other kind.stagestringrequiredone ofkeywords,searching,filtering,analysing,writing_digestkeywordsintegervalue at least 0searchesintegervalue at least 0foundintegervalue at least 0judgedintegervalue at least 0keptintegervalue at least 0dropped_off_topicintegervalue at least 0dropped_unsureintegervalue at least 0shortlistedintegervalue at least 0analysedintegervalue at least 0updated_atstringrequired
202 · Example
{
"run": {
"id": "run_8f2c1ad04b",
"kind": "video_intelligence",
"status": "running",
"created_at": "2026-09-08T09:14:02.418Z",
"completed_at": null,
"charge_credits": 0,
"list_price_credits": 0,
"cached": false
}
}400 Bad Request
invalid_request (a malformed id, wait, estimate or Idempotency-Key) or unsupported_platform, refused before any Run opens. Nothing is charged.
object
RFC 9457 problem document, served as application/problem+json. Branch on code, never on the prose.
typestringrequiredtitlestringrequiredstatusintegerrequiredcodestringrequiredone ofmissing_authorization,invalid_key,key_revoked,invalid_token,organization_not_selected,organization_access_revoked,connection_revoked,forbidden,invalid_request,unsupported_platform,unsupported_kind,kind_deprecated,not_found,video_too_long,idempotency_conflict,run_not_ready,advertiser_ambiguous,insufficient_balance,rate_limit_exceeded,concurrency_limit_exceeded,tracker_limit_exceeded,webhook_limit_exceeded,webhook_rotation_in_progress,collection_limit_exceeded,collection_name_taken,collection_kind_mismatch,media_unavailable,vendor_error,source_unavailable,service_unavailable,internal_errorStable machine-readable code.detailstring
400 · Example
{
"type": "https://www.shortsintel.com/docs/reference/errors/invalid_request",
"title": "Invalid request",
"status": 400,
"code": "invalid_request",
"detail": "`estimate` must be `true` or `false`."
}401 Unauthorized
missing_authorization, invalid_key, key_revoked or invalid_token.
object
RFC 9457 problem document, served as application/problem+json. Branch on code, never on the prose.
typestringrequiredtitlestringrequiredstatusintegerrequiredcodestringrequiredone ofmissing_authorization,invalid_key,key_revoked,invalid_token,organization_not_selected,organization_access_revoked,connection_revoked,forbidden,invalid_request,unsupported_platform,unsupported_kind,kind_deprecated,not_found,video_too_long,idempotency_conflict,run_not_ready,advertiser_ambiguous,insufficient_balance,rate_limit_exceeded,concurrency_limit_exceeded,tracker_limit_exceeded,webhook_limit_exceeded,webhook_rotation_in_progress,collection_limit_exceeded,collection_name_taken,collection_kind_mismatch,media_unavailable,vendor_error,source_unavailable,service_unavailable,internal_errorStable machine-readable code.detailstring
401 · Example
{
"type": "https://www.shortsintel.com/docs/reference/errors/invalid_key",
"title": "Invalid key",
"status": 401,
"code": "invalid_key",
"detail": "That API key does not exist. Keys look like `sk_live_...` and are created in the console."
}402 Payment Required
insufficient_balance — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends partial.
object
RFC 9457 problem document, served as application/problem+json. Branch on code, never on the prose.
typestringrequiredtitlestringrequiredstatusintegerrequiredcodestringrequiredone ofmissing_authorization,invalid_key,key_revoked,invalid_token,organization_not_selected,organization_access_revoked,connection_revoked,forbidden,invalid_request,unsupported_platform,unsupported_kind,kind_deprecated,not_found,video_too_long,idempotency_conflict,run_not_ready,advertiser_ambiguous,insufficient_balance,rate_limit_exceeded,concurrency_limit_exceeded,tracker_limit_exceeded,webhook_limit_exceeded,webhook_rotation_in_progress,collection_limit_exceeded,collection_name_taken,collection_kind_mismatch,media_unavailable,vendor_error,source_unavailable,service_unavailable,internal_errorStable machine-readable code.detailstring
402 · Example
{
"type": "https://www.shortsintel.com/docs/reference/errors/insufficient_balance",
"title": "Insufficient balance",
"status": 402,
"code": "insufficient_balance",
"detail": "Your balance does not cover this operation. Top up in the console.",
"balance_credits": 0,
"required_credits": 23
}409 Conflict
idempotency_conflict — the same Idempotency-Key with a different payload.
object
RFC 9457 problem document, served as application/problem+json. Branch on code, never on the prose.
typestringrequiredtitlestringrequiredstatusintegerrequiredcodestringrequiredone ofmissing_authorization,invalid_key,key_revoked,invalid_token,organization_not_selected,organization_access_revoked,connection_revoked,forbidden,invalid_request,unsupported_platform,unsupported_kind,kind_deprecated,not_found,video_too_long,idempotency_conflict,run_not_ready,advertiser_ambiguous,insufficient_balance,rate_limit_exceeded,concurrency_limit_exceeded,tracker_limit_exceeded,webhook_limit_exceeded,webhook_rotation_in_progress,collection_limit_exceeded,collection_name_taken,collection_kind_mismatch,media_unavailable,vendor_error,source_unavailable,service_unavailable,internal_errorStable machine-readable code.detailstring
409 · Example
{
"type": "https://www.shortsintel.com/docs/reference/errors/idempotency_conflict",
"title": "Idempotency conflict",
"status": 409,
"code": "idempotency_conflict",
"detail": "This Idempotency-Key was used with a different payload.",
"run_id": "run_8f2c1ad04b"
}429 Too Many Requests
rate_limit_exceeded or concurrency_limit_exceeded, with Retry-After.
object
RFC 9457 problem document, served as application/problem+json. Branch on code, never on the prose.
typestringrequiredtitlestringrequiredstatusintegerrequiredcodestringrequiredone ofmissing_authorization,invalid_key,key_revoked,invalid_token,organization_not_selected,organization_access_revoked,connection_revoked,forbidden,invalid_request,unsupported_platform,unsupported_kind,kind_deprecated,not_found,video_too_long,idempotency_conflict,run_not_ready,advertiser_ambiguous,insufficient_balance,rate_limit_exceeded,concurrency_limit_exceeded,tracker_limit_exceeded,webhook_limit_exceeded,webhook_rotation_in_progress,collection_limit_exceeded,collection_name_taken,collection_kind_mismatch,media_unavailable,vendor_error,source_unavailable,service_unavailable,internal_errorStable machine-readable code.detailstring
429 · Example
{
"type": "https://www.shortsintel.com/docs/reference/errors/rate_limit_exceeded",
"title": "Rate limit exceeded",
"status": 429,
"code": "rate_limit_exceeded",
"detail": "60 requests per minute per key. Retry after the number of seconds in `Retry-After`.",
"limit": 60
}502 Bad Gateway
A Run that opened and then failed is answered as application/problem+json with its problem's status — vendor_error here, not_found as 404 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged.
object
RFC 9457 problem document, served as application/problem+json. Branch on code, never on the prose.
typestringrequiredtitlestringrequiredstatusintegerrequiredcodestringrequiredone ofmissing_authorization,invalid_key,key_revoked,invalid_token,organization_not_selected,organization_access_revoked,connection_revoked,forbidden,invalid_request,unsupported_platform,unsupported_kind,kind_deprecated,not_found,video_too_long,idempotency_conflict,run_not_ready,advertiser_ambiguous,insufficient_balance,rate_limit_exceeded,concurrency_limit_exceeded,tracker_limit_exceeded,webhook_limit_exceeded,webhook_rotation_in_progress,collection_limit_exceeded,collection_name_taken,collection_kind_mismatch,media_unavailable,vendor_error,source_unavailable,service_unavailable,internal_errorStable machine-readable code.detailstring
502 · Example
{
"type": "https://www.shortsintel.com/docs/reference/errors/vendor_error",
"title": "Vendor error",
"status": 502,
"code": "vendor_error",
"detail": "The upstream data provider could not be reached.",
"run": {
"id": "run_8f2c1ad04b",
"kind": "video_intelligence",
"status": "failed",
"created_at": "2026-09-08T09:14:02.418Z",
"completed_at": "2026-09-08T09:14:04.930Z",
"charge_credits": 0,
"list_price_credits": 0,
"cached": false
}
}503 Service Unavailable
source_unavailable: every source this Run reads failed or is switched off. The Run fails uncharged, with its envelope alongside; endpoints names the source. Retry later rather than treating it as empty.
object
RFC 9457 problem document, served as application/problem+json. Branch on code, never on the prose.
typestringrequiredtitlestringrequiredstatusintegerrequiredcodestringrequiredone ofmissing_authorization,invalid_key,key_revoked,invalid_token,organization_not_selected,organization_access_revoked,connection_revoked,forbidden,invalid_request,unsupported_platform,unsupported_kind,kind_deprecated,not_found,video_too_long,idempotency_conflict,run_not_ready,advertiser_ambiguous,insufficient_balance,rate_limit_exceeded,concurrency_limit_exceeded,tracker_limit_exceeded,webhook_limit_exceeded,webhook_rotation_in_progress,collection_limit_exceeded,collection_name_taken,collection_kind_mismatch,media_unavailable,vendor_error,source_unavailable,service_unavailable,internal_errorStable machine-readable code.detailstring
503 · Example
{
"type": "https://www.shortsintel.com/docs/reference/errors/source_unavailable",
"title": "Source unavailable",
"status": 503,
"code": "source_unavailable",
"detail": "The tiktok/video source is unavailable right now.",
"endpoints": [
"tiktok/video"
],
"run": {
"id": "run_8f2c1ad04b",
"kind": "video_intelligence",
"status": "failed",
"created_at": "2026-09-08T09:14:02.418Z",
"completed_at": "2026-09-08T09:14:04.930Z",
"charge_credits": 0,
"list_price_credits": 0,
"cached": false
}
}Request samples
curl -X POST "https://www.shortsintel.com/v1/videos/tiktok/7300000000000000000/intelligence?wait=30" \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"refresh":true}'const response = await fetch(
"https://www.shortsintel.com/v1/videos/tiktok/7300000000000000000/intelligence?wait=30",
{
method: "POST",
headers: {
"Authorization": "Bearer sk_live_YOUR_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
"refresh": true
}),
},
);
if (!response.ok) {
throw new Error(`ShortsIntel ${response.status}`);
}
const data = await response.json();import requests
response = requests.post(
"https://www.shortsintel.com/v1/videos/tiktok/7300000000000000000/intelligence?wait=30",
headers={
"Authorization": "Bearer sk_live_YOUR_KEY",
"Content-Type": "application/json",
},
json={
"refresh": True,
},
)
response.raise_for_status()
data = response.json()Related guides
This call creates a Run. With wait it holds the response open until the Run finishes or the wait runs out, then answers with the finished result or 202 and the running Run. Poll GET /v1/runs/{id} until it settles; re-reading a Run is free.
- Runs: How a call becomes a Run, waiting inline, polling and free re-reads.
- Pricing and estimates: Credits, estimates and why a cached result costs the same as a fresh one.
- Addressing videos and creators: Naming a video, creator, hashtag or sound.
- Analysis: What we derive from a video, why it is computed once and reused, and when asking for a refresh is charged as new work.
- Scores: Outlier and velocity, how a creator baseline is built, what confidence means, and when a score stops being reported.