Get a research
/v1/research/{id}What a research recorded about itself: the intent, the keywords derived from it and why, the coverage each platform reported, the requested, completed, failed, cached and fresh counts, and the digest. No item lists — the videos, creators, hashtags and sounds behind the digest page separately. A research id is its Run id, so this returns the Run envelope: a research still running answers 202 { run } with status running, exactly as polling does, rather than an error, and one that failed answers as a failed Run polls: application/problem+json with the problem beside the envelope. Another Customer's id, and an id belonging to a Run of another kind, are the same 404.
Open in Playground(no API Key needed)
Price
Free, forever. 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. |
Responses
200 OK
{ run, result } once the research has settled.
object
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
resultobjectrequiredWhat a research recorded about itself: the intent, the keywords derived from it, coverage per platform, the counts, and the digest. The videos, creators, hashtags and sounds behind it page separately.digest_versionintegerrequiredintentstringrequiredkeywordsarray of stringrequiredkeyword_rationalestringrequiredcoveragemap of stringrequireddigestobjectrequiredsummarystringrequiredTwo or three sentences answering the intent directly: what is working on this topic right now, across the videos analysed.themesarray of objectrequireditems 1 to 8namestringrequiredShort label, three or four words.descriptionstringrequiredWhat the videos in this theme have in common.video_idsarray of stringrequiredvideo_idkeys (platform:id) of the analysed videos that belong to this theme, exactly as given in the evidence.
hooksarray of objectrequireditems at most 12textstringrequiredThe opening line, quoted VERBATIM from the evidence. Never paraphrase, never invent.video_idstringrequiredvideo_idkey (platform:id) of the video this hook is quoted from, exactly as given in the evidence.hook_typestringrequiredThe kind of hook, e.g. question, claim, negation, stat.why_it_worksstringrequiredOne sentence.templatestringlength at least 1The verbatim hook with its specifics (product, number, mistake, outcome, audience) replaced by short bracketed slots, every other word kept as spoken, e.g. "Stop [mistake] if you want [outcome]".liftobjectnullableThelift_tables.hook_typerow for the quoted video's analysed hook type, attached in code (never written by the model). Null when that video has no analysed hook type.valuestringrequiredThe analysed hook type, or format + hook type, the row is keyed by.video_countintegerrequiredmedian_viewsnumberrequirednullablemedian_engagement_ratenumberrequirednullableviews_liftnumberrequirednullableengagement_liftnumberrequirednullablelow_confidencebooleanrequired
sentimentobjectrequiredpositivenumberrequiredvalue 0 to 1neutralnumberrequiredvalue 0 to 1negativenumberrequiredvalue 0 to 1notestringrequiredOne sentence on what drives the distribution.
anglesarray of objectrequireditems 1 to 8anglestringrequiredA content angle worth trying.rationalestringrequiredWhy the evidence supports it, referencing what was seen.
formulasarray of objectitems at most 5namestringrequiredShort label, three to five words.recipestringrequiredOne line saying how to make it: the format, how it opens, and the order of its beats.visual_formatstringrequiredThe visual format, copied exactly as it appears inlift_tables.visual_format.hook_typestringrequiredThe hook type, copied exactly as it appears inlift_tables.hook_type.structurestringrequiredThe beat structure the example videos share, as short beats joined by arrows, e.g. "problem → demo → result → CTA".video_idsarray of stringrequiredUp to 3video_idkeys (platform:id) of analysed videos that follow this formula, best performing first, exactly as given in the evidence.liftobjectrequirednullableThelift_tables.format_hookrow for this formula's visual format and hook type, attached in code (never written by the model): itsvideo_countand lifts. The lift is for the format + hook type pair only; the structure is not bucketed. Null when no analysed video has that pair.valuestringrequiredThe analysed hook type, or format + hook type, the row is keyed by.video_countintegerrequiredmedian_viewsnumberrequirednullablemedian_engagement_ratenumberrequirednullableviews_liftnumberrequirednullableengagement_liftnumberrequirednullablelow_confidencebooleanrequired
gapsarray of objectitems at most 5Up to 5 things the audience asks for in the top 10 videos' comments that no analysed video addresses, each with verbatim quoted comments (and the video they came from) and a suggested angle. Empty when no comments were available. Absent on research recorded before gaps existed.askstringrequiredWhat the audience asks for or complains about, in one sentence, in your own words.commentsarray of objectrequireditems 1 to 3textstringrequiredA comment quoted VERBATIM from <comments>, character for character. Never paraphrase, never invent.video_idstringrequiredvideo_idkey (platform:id) of the video this comment was left under, exactly as given in <comments>.
anglestringrequiredA video someone could make that answers this ask, in one sentence.
lift_tablesobjectLift tables computed in code over the analysed videos: per visual format, hook type and length bucket, the video count, median views and engagement rate, and lift versus the whole set's median. The Digest quotes lifts only from these. Absent on research recorded before they existed.setobjectrequiredvideo_countintegerrequiredmedian_viewsnumberrequirednullablemedian_engagement_ratenumberrequirednullable
visual_formatarray of objectrequiredvaluestringrequiredThe visual format, hook type, length bucket, or<format> + <hook type>.video_countintegerrequiredmedian_viewsnumberrequirednullablemedian_engagement_ratenumberrequirednullable(likes + comments + shares + saves) / views.views_liftnumberrequirednullableMedian views of this row over the whole set's median views. Null when the set median is 0 or unknown.engagement_liftnumberrequirednullablelow_confidencebooleanrequiredTrue when the row rests on fewer than 3 videos.
hook_typearray of objectrequiredvaluestringrequiredThe visual format, hook type, length bucket, or<format> + <hook type>.video_countintegerrequiredmedian_viewsnumberrequirednullablemedian_engagement_ratenumberrequirednullable(likes + comments + shares + saves) / views.views_liftnumberrequirednullableMedian views of this row over the whole set's median views. Null when the set median is 0 or unknown.engagement_liftnumberrequirednullablelow_confidencebooleanrequiredTrue when the row rests on fewer than 3 videos.
lengtharray of objectrequiredvaluestringrequiredThe visual format, hook type, length bucket, or<format> + <hook type>.video_countintegerrequiredmedian_viewsnumberrequirednullablemedian_engagement_ratenumberrequirednullable(likes + comments + shares + saves) / views.views_liftnumberrequirednullableMedian views of this row over the whole set's median views. Null when the set median is 0 or unknown.engagement_liftnumberrequirednullablelow_confidencebooleanrequiredTrue when the row rests on fewer than 3 videos.
format_hookarray of objectVisual format and hook type together,valueas<format> + <hook type>: the rows the Digest's formulas take their lift from. Absent on research recorded before it existed.valuestringrequiredThe visual format, hook type, length bucket, or<format> + <hook type>.video_countintegerrequiredmedian_viewsnumberrequirednullablemedian_engagement_ratenumberrequirednullable(likes + comments + shares + saves) / views.views_liftnumberrequirednullableMedian views of this row over the whole set's median views. Null when the set median is 0 or unknown.engagement_liftnumberrequirednullablelow_confidencebooleanrequiredTrue when the row rests on fewer than 3 videos.
researchobjectrequiredrequestedintegerrequiredcompletedintegerrequiredfailedintegerrequiredcachedintegerrequiredfreshintegerrequiredmax_videosintegerrequiredincluded_videosintegerrequireddropped_off_topicintegerCandidates judged off-topic and dropped before the shortlist; never charged.dropped_unsureintegerCandidates whose relevance was unsure and that ranked too low to keep; never charged.dropped_samplearray of objectUp to 10 dropped candidates, best-ranked first, with why each was dropped. Absent on research recorded before it existed.video_idstringrequiredplatform:idof the dropped video.verdictstringrequiredone ofoff_topic,unsurereasonstringrequiredThe relevance check's reason, short.
200 · `{ run, result }` once the research has settled.
{
"run": {
"id": "run_c07a41bb92",
"kind": "research",
"status": "partial",
"created_at": "2026-09-08T09:14:02.418Z",
"completed_at": "2026-09-08T09:14:04.930Z",
"charge_credits": 500,
"list_price_credits": 500,
"cached": false
},
"result": {
"digest_version": 2,
"intent": "why are cold plunge videos taking off",
"keywords": [
"cold plunge",
"ice bath",
"cold exposure",
"cold plunge routine"
],
"keyword_rationale": "The intent is about a practice rather than a product, so the keywords cover the practice and its two common synonyms.",
"coverage": {
"tiktok": "complete",
"instagram": "partial"
},
"digest": {
"summary": "Cold plunge videos are travelling on measured personal diaries rather than on science explainers. The videos that break out open on an unimpressive first number and pay it off with a later one, and keep the discomfort on camera instead of cutting around it. Sentiment is broadly positive, with the negative share concentrated on the cost of equipment.",
"themes": [
{
"name": "30-day diaries",
"share": 0.45,
"description": "One creator, one tub, a counter on screen, and the change stated as a number rather than a feeling.",
"video_ids": [
"tiktok:7300000000000000000"
]
},
{
"name": "Before-workout plunging",
"share": 0.2,
"description": "Shorter clips arguing about timing relative to training, usually with a gym setting.",
"video_ids": [
"tiktok:7300000000000000042"
]
}
],
"hooks": [
{
"text": "Day one I lasted eleven seconds.",
"video_id": "tiktok:7300000000000000000",
"hook_type": "stat",
"why_it_works": "An unimpressive concrete number sets up a payoff the viewer stays for.",
"template": "Day one I lasted [small number] [unit].",
"lift": {
"value": "STORY",
"video_count": 9,
"median_views": 1840000,
"median_engagement_rate": 0.0912,
"views_lift": 1.6,
"engagement_lift": 1.21,
"low_confidence": false
}
}
],
"sentiment": {
"positive": 0.62,
"neutral": 0.23,
"negative": 0.15,
"note": "Enthusiasm for the routine itself; the negative share is almost entirely the price of a tub."
},
"angles": [
{
"angle": "Open on the day-30 number and work backwards to day one.",
"rationale": "Every diary in the shortlist that led with the outcome outperformed the ones that led with the method."
}
],
"gaps": [
{
"ask": "Viewers want a cold plunge setup that does not cost hundreds.",
"comments": [
{
"text": "ok but what do I do if I can't afford a 600 dollar tub",
"video_id": "tiktok:7300000000000000000"
}
],
"angle": "A 30-day diary on a chest freezer or bathtub build, with the receipt on screen."
}
]
},
"research": {
"requested": 50,
"completed": 48,
"failed": 2,
"cached": 11,
"fresh": 37,
"max_videos": 50,
"included_videos": 50
}
}
}202 Accepted
{ run } while the research is still collecting — the same body GET /v1/runs/{id} returns, not an error. run.progress says how far it has got.
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_c07a41bb92",
"kind": "research",
"status": "running",
"created_at": "2026-09-08T09:14:02.418Z",
"completed_at": null,
"charge_credits": 0,
"list_price_credits": 0,
"cached": false,
"progress": {
"stage": "analysing",
"keywords": 6,
"searches": 12,
"found": 214,
"judged": 100,
"kept": 81,
"dropped_off_topic": 17,
"dropped_unsure": 2,
"shortlisted": 50,
"analysed": 32,
"updated_at": "2026-09-08T09:14:41.007Z"
}
}
}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."
}502 Bad Gateway
A research whose Run failed is answered as application/problem+json with its problem's status, the problem members beside the unchanged Run envelope — exactly as GET /v1/runs/{id} answers it.
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_c07a41bb92",
"kind": "research",
"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 "https://www.shortsintel.com/v1/research/id_example" \
-H "Authorization: Bearer sk_live_YOUR_KEY"const response = await fetch(
"https://www.shortsintel.com/v1/research/id_example",
{
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",
headers={
"Authorization": "Bearer sk_live_YOUR_KEY",
},
)
response.raise_for_status()
data = response.json()Related guides
This call creates a Run. It can answer 202 with the running Run. Poll GET /v1/runs/{id} until it settles; re-reading a Run is free.