List a research's videos
/v1/research/{id}/videosThe videos a research collected, filtered and ordered however you like. Ask a narrower question of a research you already paid for — TikTok only, over 100k views, best outlier first — instead of running another one. Filters compose. Paging is limit/offset with a total, not the next_cursor the rest of this API uses: a research is a frozen set of at most 200 rows and you choose the ordering, so a count is cheap and more useful than a cursor. An offset past the end is an empty page with the true total, not an error. Under as_of=run (the default) the numbers are frozen as the research saw them, so this list always agrees with the digest; under as_of=latest they are the newest this API holds and may not. as_of on the response says which you got. A research that has not finished answers 409 run_not_ready.
Open in Playground(no API Key needed)
Price
Free, forever, filtered or not. Collection is the only thing charged.
Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
id | path | string | Required | — | Research id — the id of the Run that collected it. |
min_views | query | integermin 0 | Optional | — | Keep videos with at least this many views. A video with no view count is excluded rather than assumed to be at zero. |
max_views | query | integermin 0 | Optional | — | Keep videos with at most this many views. |
platforms | query | string | Optional | — | Comma-separated, or repeated, each one of tiktok, instagram, youtube, facebook; any other value is a 400. Omit for every platform the research covered. There is no per-video region: a research collects under one region. |
start_date | query | string | Optional | — | Keep videos posted at or after this ISO 8601 date (2026-01-01, midnight UTC) or date-time with Z or an explicit offset (2026-01-01T00:00:00Z). A video with no posted date is excluded. |
end_date | query | string | Optional | — | Keep videos posted at or before this ISO 8601 date or date-time (a date-time needs Z or an explicit offset). A date alone (2026-01-31) includes the whole of that UTC day; a date-time is the exact instant. A video with no posted date is excluded. |
creator | query | string | Optional | — | A creator id or handle, with or without the @, matched case-insensitively. |
hashtag | query | string | Optional | — | A single hashtag, with or without the leading #. |
sound | query | string | Optional | — | A sound id, as sound.id carries it. |
order_by | query | stringone of rank, outlier, views, posted_at | Optional | rank | rank is the research's own ordering — outlier, then views — which is what the digest was written from. It is that frozen order under either as_of: latest does not re-rank, so use order_by=outlier for the order over the numbers actually served. Ties break on platform:id, so paging never drops or repeats a video. |
sort | query | stringone of asc, desc | Optional | desc | Direction. desc is best-first for every order_by. Videos missing the value being sorted on come last in both directions: unknown is not the same as smallest. |
limit | query | integermin 1; max 100 | Optional | 25 | Videos per page. Over the maximum, or negative, is a 400 rather than a silent clamp. |
offset | query | integermin 0 | Optional | 0 | Videos to skip. Past the end returns an empty page with the true total. |
as_of | query | stringone of run, latest | Optional | run | Which snapshots to read. run (the default) is what the research itself saw, frozen, so the list always agrees with the digest. latest serves the newest snapshot this API holds for each video instead, and the filters and ordering then run over those numbers — a min_ filter under latest means "over that many now". Under latest, outlier and outlier_p90 are recomputed against the research's baseline; velocity and percentile stay as the research measured them. Be aware that latest is uneven until you refresh: a video is only as fresh as the last time anything looked at it, and a video nothing has looked at since keeps the numbers the research recorded rather than coming back empty. POST /research/{id}/refresh brings its refreshable videos (TikTok today) up to date; the rest keep what the research recorded. Every response states the basis it served. |
Responses
200 OK
{ items, total, limit, offset, as_of }.
object
itemsarray of objectrequiredplatformstringrequiredidstringrequiredurlstringrequirednullabledescriptionstringrequiredposted_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.rankintegerrequiredPosition in the research's own ranking, best first from 0 — outlier, then views.item_idstringrequirednullableThe run item this video was delivered by, if still present.cachedbooleanrequiredWhether the analysis was already stored rather than paid for.
totalintegerrequiredRows matching the filters, beforelimitandoffset.limitintegerrequiredoffsetintegerrequiredas_ofstringrequiredone ofrun,latestWhich snapshots the numbers come from:runis what the research itself saw, frozen, so a read always agrees with the digest;latestis the newest snapshot this API holds for each video.
200 · `{ items, total, limit, offset, as_of }`.
{
"items": [
{
"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
}
},
"rank": 0,
"item_id": "ri_5c30f18ab4",
"cached": true
},
{
"platform": "tiktok",
"id": "7300000000000000042",
"url": "https://www.tiktok.com/@anotherone/video/7300000000000000042",
"description": "I tried cold plunging before every workout for a week. #coldplunge",
"posted_at": "2026-09-06T13:04:52.000Z",
"duration_seconds": 28,
"thumbnail_url": "https://p16-sign.tiktokcdn.com/obj/tos-cover-7300000000000000042.jpeg",
"hashtags": [
"coldplunge",
"gym"
],
"sound": {
"id": "7180000000000000099",
"title": "Winter Air"
},
"creator": {
"id": "cre_1a77c0b3de",
"handle": "anotherone",
"name": "Another One",
"follower_count": 21400
},
"snapshot": {
"at": "2026-09-08T09:14:04.219Z",
"views": 96200,
"likes": 8140,
"comments": 212,
"shares": 401,
"saves": 1180
},
"scores": null,
"rank": 1,
"item_id": "ri_5c30f18ab5",
"cached": false
}
],
"total": 48,
"limit": 25,
"offset": 0,
"as_of": "run"
}400 Bad Request
invalid_request — an unknown platform, a min_views or max_views that is not a non-negative integer, an unparseable date, an unknown order_by, sort or as_of, a limit outside 1 to 100, or a negative or non-integer offset.
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": "`limit` must be an integer between 1 and 100, and `offset` a non-negative integer."
}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."
}404 Not Found
not_found.
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
404 · Example
{
"type": "https://www.shortsintel.com/docs/reference/errors/not_found",
"title": "Not found",
"status": 404,
"code": "not_found",
"detail": "No research with that id for this Customer."
}409 Conflict
run_not_ready — the research is still running, or failed with nothing to serve. If it is still running, poll GET /v1/runs/{id} and read again once it settles; a failed research never becomes readable.
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/run_not_ready",
"title": "Run not ready",
"status": 409,
"code": "run_not_ready",
"detail": "Research `run_c07a41bb92` is still running. Poll GET /v1/runs/run_c07a41bb92 until it reaches a terminal status."
}Request samples
curl "https://www.shortsintel.com/v1/research/id_example/videos?platforms=tiktok%2Cyoutube&start_date=2026-01-01&end_date=2026-01-31&order_by=rank&sort=desc&limit=25&offset=0&as_of=run" \
-H "Authorization: Bearer sk_live_YOUR_KEY"const response = await fetch(
"https://www.shortsintel.com/v1/research/id_example/videos?platforms=tiktok%2Cyoutube&start_date=2026-01-01&end_date=2026-01-31&order_by=rank&sort=desc&limit=25&offset=0&as_of=run",
{
method: "GET",
headers: {
"Authorization": "Bearer sk_live_YOUR_KEY",
},
},
);
if (!response.ok) {
throw new Error(`ShortsIntel ${response.status}`);
}
const data = await response.json();import requests
response = requests.get(
"https://www.shortsintel.com/v1/research/id_example/videos?platforms=tiktok%2Cyoutube&start_date=2026-01-01&end_date=2026-01-31&order_by=rank&sort=desc&limit=25&offset=0&as_of=run",
headers={
"Authorization": "Bearer sk_live_YOUR_KEY",
},
)
response.raise_for_status()
data = response.json()Related guides
- Research: How an intent becomes a digest, why a research id is a run id, and how to re-slice what you collected for free instead of paying for it twice.