API documentation
A daily list of titles heating up on zero Top 10s, with a measured hit rate, plus the charts we score them against. Plain JSON, human-readable identifiers, cache-friendly responses.
Base URL
https://streamrank.io/v1
Format
JSON over HTTPS
Auth
Bearer API key
Quickstart
- Make your first request. No key needed in the free scope:
curl "https://streamrank.io/v1/trending/breakouts?country=US"You get today's US breakout picks, the why lines, and the track record:
{
"data": {
"scope": "US",
"date": "2026-09-15",
"items": [
{
"position": 1,
"score": 45.4,
"score_change": 5.2,
"status": "rising",
"signals": {
"chart_momentum": 0,
"demand": 83.1,
"attention": 45.2
},
"why": [
"Lands Sep 19 on Netflix.",
"Demand 83 of 100.",
"Score up 5.2 today.",
"On no Top 10 in United States."
],
"premiere": "2026-09-19",
"providers": [
"netflix"
],
"outcome": {
"first_chart_date": "2026-09-20",
"first_chart_provider": "netflix",
"first_chart_country": "US",
"first_chart_rank": 4,
"days_to_chart": 5,
"peak_rank": 2,
"days_on_chart": 6
},
"missed": false,
"title": {
"id": "ttl_9q2h7v3zkm1d",
"slug": "obsession-2026",
"name": "Obsession",
"type": "movie",
"year": 2026,
"tmdb_id": 1364532,
"imdb_id": "tt31520893",
"poster_url": "https://image.tmdb.org/t/p/w500/obsession.jpg"
}
}
],
"track_record": {
"scope": "US",
"from": "2026-08-02",
"to": "2026-08-31",
"window_days": 14,
"long_window_days": 30,
"picks": 300,
"charted_7d": 41,
"charted_14d": 68,
"hit_rate_14d": 22.7,
"median_days_to_chart": 6,
"median_peak_rank": 4,
"median_days_on_chart": 5,
"picks_30d": 140,
"charted_30d": 44,
"hit_rate_30d": 31.4,
"pool": 2210,
"pool_charted_14d": 198,
"pool_hit_rate_14d": 9,
"pool_30d": 1030,
"pool_charted_30d": 154,
"pool_hit_rate_30d": 15,
"by_type": {
"tv": {
"picks": 96,
"charted_7d": 29,
"charted_14d": 41,
"hit_rate_14d": 42.7,
"median_days_to_chart": 4,
"median_peak_rank": 3,
"median_days_on_chart": 7,
"picks_30d": 45,
"charted_30d": 21,
"hit_rate_30d": 46.7,
"pool": 640,
"pool_charted_14d": 122,
"pool_hit_rate_14d": 19.1,
"pool_30d": 300,
"pool_charted_30d": 66,
"pool_hit_rate_30d": 22
},
"movie": {
"picks": 204,
"charted_7d": 12,
"charted_14d": 27,
"hit_rate_14d": 13.2,
"median_days_to_chart": 9,
"median_peak_rank": 6,
"median_days_on_chart": 3,
"picks_30d": 95,
"charted_30d": 23,
"hit_rate_30d": 24.2,
"pool": 1570,
"pool_charted_14d": 76,
"pool_hit_rate_14d": 4.8,
"pool_30d": 730,
"pool_charted_30d": 88,
"pool_hit_rate_30d": 12.1
}
},
"control": {
"picks": 300,
"charted_7d": 18,
"charted_14d": 31,
"hit_rate_14d": 10.3,
"median_days_to_chart": 7,
"median_peak_rank": 7,
"median_days_on_chart": 2,
"picks_30d": 140,
"charted_30d": 22,
"hit_rate_30d": 15.7,
"pool": 2210,
"pool_charted_14d": 198,
"pool_hit_rate_14d": 9,
"pool_30d": 1030,
"pool_charted_30d": 154,
"pool_hit_rate_30d": 15
}
}
},
"meta": {
"count": 1,
"limit_max": 10,
"history_days": 30
}
}Picks and charts update once a day. Cache responses. Create an account for more countries, history, and a live key. Top 10s are at GET /v1/charts.
curl "https://streamrank.io/v1/charts?provider=netflix&country=US&type=movie" \
-H "Authorization: Bearer sk_live_…"Authentication
Send your key in the Authorization header. Live keys start with sk_live_ and count toward your plan. Test keys start with sk_test_, return real data in the free scope, and never count toward quota.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxRequests without a key work in the free scope (United States; Netflix; current charts) at 10 requests per minute and 200 per calendar month per IP, so you can try the API before signing up.
| Plan | Countries | Providers | History | Trending list | Requests / mo | Alerts |
|---|---|---|---|---|---|---|
| Free | US | netflix | Current only | Top 10 | 1,000 | 3 titles, email |
| Developer | All | All | 30 days | Top 100 | 50,000 | 3 titles, email |
| Intelligence | All | All | Full | Top 100 | 200,000 | 200 titles + markets, email, webhook, slack |
Playground
Real requests against the live API. Runs in the free scope until you add a key.
Without a key, requests use the free scope: United States; Netflix; current charts.
/v1/charts?provider=netflix&country=UScurl "https://streamrank.io/v1/charts?provider=netflix&country=US"Run a request to see the response.Breakouts
Titles on zero Top 10 charts in the scope with demand of 40 or more from two feeds, off the charts for 30 days. TV ranks by demand level, movies by score change, fading titles last. Each item has why and, once it charts within 30 days, outcome. track_record is the 14-day and 30-day hit rate of past picks next to the rate for every eligible title.
| country | string | Optional. ISO 3166-1 alpha-2. Omit it for the global list. A country scope means zero Top 10 charts in that country. |
| type | movie | tv | Optional. Keep only movies or only TV shows. |
| limit | integer | Optional. 1–50, default 10, capped by your plan's trending limit. |
| date | YYYY-MM-DD | Optional. The newest pick list on or before this date, inside your plan's history window. |
GET /v1/trending/breakouts
GET /v1/trending/breakouts?country=US&type=tv
GET /v1/trending/breakouts?country=GB&date=2026-09-10{
"data": {
"scope": "US",
"date": "2026-09-15",
"items": [
{
"position": 1,
"score": 45.4,
"score_change": 5.2,
"status": "rising",
"signals": {
"chart_momentum": 0,
"demand": 83.1,
"attention": 45.2
},
"why": [
"Lands Sep 19 on Netflix.",
"Demand 83 of 100.",
"Score up 5.2 today.",
"On no Top 10 in United States."
],
"premiere": "2026-09-19",
"providers": [
"netflix"
],
"outcome": {
"first_chart_date": "2026-09-20",
"first_chart_provider": "netflix",
"first_chart_country": "US",
"first_chart_rank": 4,
"days_to_chart": 5,
"peak_rank": 2,
"days_on_chart": 6
},
"missed": false,
"title": {
"id": "ttl_9q2h7v3zkm1d",
"slug": "obsession-2026",
"name": "Obsession",
"type": "movie",
"year": 2026,
"tmdb_id": 1364532,
"imdb_id": "tt31520893",
"poster_url": "https://image.tmdb.org/t/p/w500/obsession.jpg"
}
}
],
"track_record": {
"scope": "US",
"from": "2026-08-02",
"to": "2026-08-31",
"window_days": 14,
"long_window_days": 30,
"picks": 300,
"charted_7d": 41,
"charted_14d": 68,
"hit_rate_14d": 22.7,
"median_days_to_chart": 6,
"median_peak_rank": 4,
"median_days_on_chart": 5,
"picks_30d": 140,
"charted_30d": 44,
"hit_rate_30d": 31.4,
"pool": 2210,
"pool_charted_14d": 198,
"pool_hit_rate_14d": 9,
"pool_30d": 1030,
"pool_charted_30d": 154,
"pool_hit_rate_30d": 15,
"by_type": {
"tv": {
"picks": 96,
"charted_7d": 29,
"charted_14d": 41,
"hit_rate_14d": 42.7,
"median_days_to_chart": 4,
"median_peak_rank": 3,
"median_days_on_chart": 7,
"picks_30d": 45,
"charted_30d": 21,
"hit_rate_30d": 46.7,
"pool": 640,
"pool_charted_14d": 122,
"pool_hit_rate_14d": 19.1,
"pool_30d": 300,
"pool_charted_30d": 66,
"pool_hit_rate_30d": 22
},
"movie": {
"picks": 204,
"charted_7d": 12,
"charted_14d": 27,
"hit_rate_14d": 13.2,
"median_days_to_chart": 9,
"median_peak_rank": 6,
"median_days_on_chart": 3,
"picks_30d": 95,
"charted_30d": 23,
"hit_rate_30d": 24.2,
"pool": 1570,
"pool_charted_14d": 76,
"pool_hit_rate_14d": 4.8,
"pool_30d": 730,
"pool_charted_30d": 88,
"pool_hit_rate_30d": 12.1
}
},
"control": {
"picks": 300,
"charted_7d": 18,
"charted_14d": 31,
"hit_rate_14d": 10.3,
"median_days_to_chart": 7,
"median_peak_rank": 7,
"median_days_on_chart": 2,
"picks_30d": 140,
"charted_30d": 22,
"hit_rate_30d": 15.7,
"pool": 2210,
"pool_charted_14d": 198,
"pool_hit_rate_14d": 9,
"pool_30d": 1030,
"pool_charted_30d": 154,
"pool_hit_rate_30d": 15
}
}
},
"meta": {
"count": 1,
"limit_max": 10,
"history_days": 30
}
}Pick history
Every pick we have made in the scope, newest day first and in list order inside a day, each with the outcome we recorded. from is clamped to your plan's history window. Page with limit and offset against meta.total.
| country | string | Optional. ISO 3166-1 alpha-2. Omit it for the global list. |
| type | movie | tv | Optional. Keep only movies or only TV shows. |
| from | YYYY-MM-DD | Optional start date. Clamped to your plan's history window. |
| to | YYYY-MM-DD | Optional end date. |
| limit | 1–500 | Optional. Picks per page, default 100. |
| offset | integer | Optional. Picks to skip, default 0. Page with `meta.total`. |
GET /v1/trending/breakouts/history?country=US
GET /v1/trending/breakouts/history?country=US&type=tv&from=2026-08-01&to=2026-08-31
GET /v1/trending/breakouts/history?limit=50&offset=50{
"data": {
"scope": "US",
"from": "2026-08-16",
"to": "2026-09-15",
"items": [
{
"date": "2026-09-15",
"position": 1,
"score": 45.4,
"score_change": 5.2,
"status": "rising",
"signals": {
"chart_momentum": 0,
"demand": 83.1,
"attention": 45.2
},
"why": [
"Lands Sep 19 on Netflix.",
"Demand 83 of 100.",
"Score up 5.2 today.",
"On no Top 10 in United States."
],
"premiere": "2026-09-19",
"providers": [
"netflix"
],
"outcome": {
"first_chart_date": "2026-09-20",
"first_chart_provider": "netflix",
"first_chart_country": "US",
"first_chart_rank": 4,
"days_to_chart": 5,
"peak_rank": 2,
"days_on_chart": 6
},
"missed": false,
"title": {
"id": "ttl_9q2h7v3zkm1d",
"slug": "obsession-2026",
"name": "Obsession",
"type": "movie",
"year": 2026,
"tmdb_id": 1364532,
"imdb_id": "tt31520893",
"poster_url": "https://image.tmdb.org/t/p/w500/obsession.jpg"
}
}
]
},
"meta": {
"count": 1,
"total": 310,
"limit": 100,
"offset": 0,
"history_days": 30
}
}Charts
The Top 10 for one provider in one country. Omit type to receive both the movie and TV charts in one response.
| provider | string | Required. netflix, prime, disney, max, hulu, apple, paramount, peacock. |
| country | string | Required. ISO 3166-1 alpha-2, e.g. US, GB, JP. |
| type | movie | tv | Optional. Return one chart instead of both. |
| date | YYYY-MM-DD | Optional. A past chart. Resolves to the newest chart on or before the date. Requires history on your plan. |
| limit | 1–10 | Optional. Return only the top N. |
GET /v1/charts?provider=netflix&country=US&type=movie
GET /v1/charts?provider=netflix&country=JP&type=tv
GET /v1/charts?provider=disney&country=GB
GET /v1/charts?provider=max&country=US&type=tv&date=2026-09-01Ranking fields
| rank | integer | Position on the chart, 1–10. |
| previous_rank | integer | null | Rank the day before, or null if the title was not on yesterday's chart. |
| rank_change | integer | null | previous_rank − rank. Positive means it moved up. |
| is_new | boolean | True on the first day a title appears on this chart. |
| days_in_top_10 | integer | Total days on this chart, including today. |
| peak_rank | integer | Best rank so far on this chart. |
| first_charted_at | date | First day on this chart. |
| title | object | Canonical title: id, slug, name, type, year, tmdb_id, imdb_id, poster_url. |
We compute the derived metrics from our own stored history, so they cover the days we have collected.
Titles
Look up a title by name, IMDb id, or TMDB id. Returns a ranked list; ambiguous names are not a 404. Then call /v1/titles/{id} for current positions.
| q | string | Name, slug, ttl_ id, IMDb id, or TMDB id. Required unless imdb_id or tmdb_id is set. |
| imdb_id | string | Exact IMDb id, e.g. tt4574334. |
| tmdb_id | integer | Exact TMDB id. Pass type when a movie and a show share the number. |
| type | movie | tv | Optional. Restrict results to one kind. |
| year | integer | Optional. Prefer titles released this year when matching by name. |
| limit | 1–20 | Optional. Default 10. |
GET /v1/titles?q=The+Pitt
GET /v1/titles?imdb_id=tt4574334
GET /v1/titles?tmdb_id=1396&type=tv
GET /v1/titles?q=Ready+or+Not&year=2019{
"data": [
{
"id": "ttl_5k2m9x1qzc7v",
"slug": "the-whisper-man-2026",
"name": "The Whisper Man",
"type": "movie",
"year": 2026,
"tmdb_id": 860508,
"imdb_id": "tt11561116",
"poster_url": "https://image.tmdb.org/t/p/w500/….jpg",
"match": "name"
}
],
"meta": {
"count": 1
}
}A canonical title with where it currently ranks, plus stats over the history your plan includes. Countries and providers follow the same entitlements as /v1/charts. Accepts a title id (ttl_…), slug, IMDb id, or TMDB id.
GET /v1/titles/ttl_5k2m9x1qzc7v
GET /v1/titles/the-whisper-man-2026
GET /v1/titles/tt11561116Day-by-day ranking history for a title, newest first, clamped to your plan's history window.
| provider | string | Optional filter. |
| country | string | Optional filter. |
| from | YYYY-MM-DD | Optional start date. |
| to | YYYY-MM-DD | Optional end date. |
| limit | 1–5000 | Optional. Default 1000. |
{
"data": [
{
"date": "2026-09-10",
"provider": "netflix",
"country": "US",
"type": "movie",
"rank": 3,
"previous_rank": 1,
"rank_change": -2,
"days_in_top_10": 13,
"peak_rank": 1
}
],
"meta": {
"title_id": "ttl_5k2m9x1qzc7v",
"from": "2026-08-11",
"to": "2026-09-10",
"history_days": 30
}
}Trending
Provider-scoped signals from today's movie and TV charts: biggest_risers, new_entries, and longest_streaks. There is deliberately no cross-provider ranking: a #1 on Apple TV+ and a #5 on Netflix are not comparable.
| provider | string | Required. |
| country | string | Required. ISO 3166-1 alpha-2. |
| limit | 1–20 | Optional. Items per bucket, default 5. |
GET /v1/trending?provider=netflix&country=USTrending titles
One list that ranks titles across every service by a single trend score, 0–100. Three categories feed it: chart_momentum (position, breadth, and movement across Top 10 charts inside the scope), demand (consumption and discovery intent: first-party activity is worldwide, discovery rankings follow the country), and attention (broad web attention, worldwide). Omit country for the global list; a country scope weights that country's Top 10 charts and discovery rankings, while first-party demand and attention stay worldwide. The score is daily. First-party demand counts cover the last complete day, attention is discounted when it is flat, and titles seen only by attention rank after titles with chart or demand presence.
| country | string | Optional. ISO 3166-1 alpha-2. Omit it for the global list. A country scope weights that country's Top 10 charts and that country's discovery rankings; first-party demand and attention stay worldwide. |
| type | movie | tv | Optional. Restrict the list to movies or to shows. |
| status | new | rising | steady | peaking | fading | Optional. Keep only titles with this status. |
| limit | 1–100 | Optional. Default 10. Capped by your plan: 10 on Free, 100 on paid plans. |
| date | YYYY-MM-DD | Optional. A past list. Resolves to the newest list on or before the date. Requires history on your plan. |
GET /v1/trending/titles
GET /v1/trending/titles?country=US&type=tv
GET /v1/trending/titles?status=rising&limit=25
GET /v1/trending/titles?country=GB&date=2026-09-10{
"data": {
"scope": "global",
"date": "2026-09-10",
"items": [
{
"rank": 1,
"previous_rank": 4,
"rank_change": 3,
"score": 92.6,
"previous_score": 81.4,
"score_change": 11.2,
"status": "rising",
"chart_count": 9,
"on_charts": [
{
"provider": "netflix",
"country": "US",
"type": "movie",
"rank": 1
},
{
"provider": "netflix",
"country": "GB",
"type": "movie",
"rank": 2
}
],
"signals": {
"chart_momentum": 96.1,
"demand": 88.3,
"attention": 90.7
},
"title": {
"id": "ttl_5k2m9x1qzc7v",
"slug": "the-whisper-man-2026",
"name": "The Whisper Man",
"type": "movie",
"year": 2026,
"tmdb_id": 860508,
"imdb_id": "tt11561116",
"poster_url": "https://image.tmdb.org/t/p/w500/….jpg"
}
}
]
},
"meta": {
"count": 1,
"limit_max": 10,
"history_days": 0
}
}Trend fields
| rank | integer | Position on the trending list for this scope and day. |
| previous_rank | integer | null | Trending rank the day before, or null if the title was not on yesterday's list. |
| rank_change | integer | null | previous_rank − rank. Positive means it moved up. |
| score | number | Trend score, 0–100. Relative to every other title scored in the same scope on the same day. |
| previous_score | number | null | Yesterday's score in this scope, or null. |
| score_change | number | null | score − previous_score, one decimal place. |
| status | new | rising | steady | peaking | fading | `new`: first day on this list. `rising`: the signals are growing against their own 7-day average and the title has already reached a meaningful level (a single new chart entry does not qualify). `steady`: little day-over-day change. `peaking`: near the top of the scale and no longer growing. `fading`: the signals are shrinking. |
| chart_count | integer | How many Top 10 charts the title sits on today inside the scope. Counts every chart, not only the ones your plan can read. |
| on_charts | array | Today's chart positions: provider, country, type, rank. Filtered to the countries and providers your plan includes, so it can be shorter than chart_count. |
| signals | object | `chart_momentum`, `demand`, and `attention`, each 0–100 or null when that category had no data that day. chart_momentum is position, breadth, and movement across Top 10 charts inside the scope. demand is consumption and discovery intent: first-party activity is worldwide and counted over the last complete day; discovery rankings follow the country scope and are summed on the global list. attention is broad web attention, worldwide, discounted when it is flat: a title that draws the same attention every day keeps half its level, a spike keeps all of it. Attention alone never carries a title above titles with chart or demand presence unless it is spiking. |
| title | object | Canonical title: id, slug, name, type, year, tmdb_id, imdb_id, poster_url. |
The title's row on today's list plus its score history, newest first, clamped to your plan's history window. current is null when the title is not trending in that scope today.
| country | string | Optional. ISO 3166-1 alpha-2. Omit it for the global scope. |
| from | YYYY-MM-DD | Optional start date. Clamped to your plan's history window. |
| to | YYYY-MM-DD | Optional end date. |
| limit | 1–365 | Optional. Days of history, default 90. |
{
"data": {
"title_id": "ttl_5k2m9x1qzc7v",
"scope": "US",
"current": {
"scope": "US",
"date": "2026-09-10",
"rank": 2,
"previous_rank": 5,
"rank_change": 3,
"score": 90.1,
"previous_score": 78.9,
"score_change": 11.2,
"status": "rising",
"chart_count": 3,
"signals": {
"chart_momentum": 94,
"demand": 86.2,
"attention": 88.5
}
},
"history": [
{
"date": "2026-09-10",
"rank": 2,
"score": 90.1,
"status": "rising",
"chart_count": 3,
"signals": {
"chart_momentum": 94,
"demand": 86.2,
"attention": 88.5
}
},
{
"date": "2026-09-09",
"rank": 5,
"score": 78.9,
"status": "new",
"chart_count": 2,
"signals": {
"chart_momentum": 81.3,
"demand": 74,
"attention": null
}
}
]
},
"meta": {
"from": "2026-08-11",
"to": "2026-09-10",
"history_days": 30
}
}Title signals
What moved the score, one day per row. Each category carries its 0–100 level, the number of independent feeds that saw the title that day, and its growth against its own 7-day baseline. chart_momentum carries charts, the Top 10 charts inside the scope that day. threshold is the demand level a title must reach to be a pick, so you can read off the day it crossed. Feeds are counted, never named.
| country | string | Optional. ISO 3166-1 alpha-2. Omit it for the global scope. |
| from | YYYY-MM-DD | Optional start date. Clamped to your plan's history window. |
| to | YYYY-MM-DD | Optional end date. |
| limit | 1–365 | Optional. Days of history, default 90. |
GET /v1/titles/ttl_5k2m9x1qzc7v/signals
GET /v1/titles/the-whisper-man-2026/signals?country=US&limit=30{
"data": {
"title_id": "ttl_5k2m9x1qzc7v",
"scope": "US",
"threshold": 40,
"history": [
{
"date": "2026-09-10",
"chart_momentum": {
"value": 94,
"charts": 3
},
"demand": {
"value": 86.2,
"feeds": 2,
"growth": 0.31
},
"attention": {
"value": 88.5,
"feeds": 1,
"growth": 0.12
}
},
{
"date": "2026-09-09",
"chart_momentum": {
"value": null,
"charts": 0
},
"demand": {
"value": 38.4,
"feeds": 1,
"growth": 0.44
},
"attention": {
"value": null,
"feeds": null,
"growth": null
}
}
]
},
"meta": {
"from": "2026-08-11",
"to": "2026-09-10",
"history_days": 30
}
}Countries
Every country with chart data and the providers available in each, plus whether your plan includes it.
{
"data": [
{
"code": "US",
"name": "United States",
"providers": [
"netflix",
"prime",
"disney",
"max",
"hulu",
"apple",
"paramount",
"peacock"
],
"included_in_plan": true
}
]
}Providers
{
"data": [
{
"id": "netflix",
"name": "Netflix",
"countries": [
"AU",
"CA",
"DE",
"FR",
"GB",
"JP",
"US"
],
"included_in_plan": true
},
{
"id": "prime",
"name": "Prime Video",
"countries": [
"…"
],
"included_in_plan": true
}
]
}Alerts
Alerts tell you when a title or a market moves, so you do not poll. Set them up in the dashboard at /dashboard/alerts. Watch one title in one scope, or watch a market and receive every pick it produces.
Channels: email to your account address. Signed webhooks and Slack digests are on the Intelligence plan, $99 a month.
| title.threshold_crossed | A watched title's demand reached 40 with zero Top 10s in the scope. Yesterday it was below. Payload adds threshold, demand, previous_demand, signals, status. | |
| title.charted | A watched title entered a Top 10 inside the scope. Payload adds charts: one entry per chart it entered, each provider, country, type, rank. | |
| pick.created | A title became one of the day's ten picks. A market watch gets all ten, dated on the list day. Payload adds position, score, score_change, status, signals, why, premiere, providers. | |
| pick.charted | A pick landed on a Top 10 inside 30 days. Dated on the first chart date. Payload adds pick_date, position, first_chart_date, first_chart_provider, first_chart_country, first_chart_rank, days_to_chart. |
Every payload also carries scope, date (the chart or list day the event is about), and title (the canonical title object).
Webhooks
One POST per event, JSON body:
{
"id": "evt_9f3c1b7a4d2e",
"type": "pick.created",
"created_at": "2026-09-15T11:05:12.431Z",
"data": {
"type": "pick.created",
"scope": "US",
"date": "2026-09-15",
"position": 1,
"score": 45.4,
"score_change": 5.2,
"status": "rising",
"signals": {
"chart_momentum": 0,
"demand": 83.1,
"attention": 45.2
},
"why": [
"Lands Sep 19 on Netflix.",
"Demand 83 of 100.",
"On no Top 10 in United States."
],
"premiere": "2026-09-19",
"providers": [
"netflix"
],
"title": {
"id": "ttl_9q2h7v3zkm1d",
"slug": "obsession-2026",
"name": "Obsession",
"type": "movie",
"year": 2026,
"tmdb_id": 1364532,
"imdb_id": "tt31520893",
"poster_url": "https://image.tmdb.org/t/p/w500/obsession.jpg"
}
}
}data is the payload for the event type. Headers:
X-StreamRank-Signature: t=<unix seconds>,v1=<hex>
X-StreamRank-Event: pick.created
X-StreamRank-Delivery: dlv_1842v1 is HMAC-SHA256 over ${t}.${rawBody} with your endpoint secret. Compare it in constant time against the exact bytes you received, not a re-serialized body.
import crypto from "node:crypto";
// rawBody must be the exact bytes you received, before JSON.parse.
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1 ?? "", "hex");
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false;
// Reject anything older than five minutes.
return Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
}Respond 2xx within 10 seconds. A failed delivery is retried on the next run, up to 3 attempts. After 10 consecutive failures the endpoint is paused; re-enable it in the dashboard.
Slack
One digest post per day per endpoint, with every event since the last post.
Errors
Errors are JSON with a stable code and a human-readable message.
{
"error": {
"code": "plan_required",
"message": "Country \"JP\" is not included in the Free plan.",
"details": {
"param": "country",
"requested": "JP",
"allowed_countries": [
"US"
],
"hint": "Use country=US, or send a key for a plan that includes JP.",
"upgrade_url": "https://streamrank.io/pricing",
"docs_url": "https://streamrank.io/docs.md#authentication"
}
}
}| 400 invalid_request | Missing or malformed parameter. details.hint says how to fix it. | |
| 401 unauthorized | Malformed or revoked API key. Omit the header for the free anonymous scope. | |
| 403 plan_required | The country, provider, or date is outside your plan. details lists allowed values and upgrade_url. | |
| 404 not_found | No chart or title for that request. For titles, search GET /v1/titles?q=. | |
| 429 rate_limited | Per-minute limit hit. Honor Retry-After. Check X-RateLimit-Reset. | |
| 429 quota_exceeded | Monthly request quota hit. Honor Retry-After or upgrade. | |
| 500 internal_error | Our fault. Retry with backoff. |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. 429 responses also send Retry-After. Errors include details.docs_url and, when useful, details.hint with the next legal call.
SDKs
The API is plain HTTP, so any client works. Official SDKs are coming; until then:
const res = await fetch("https://streamrank.io/v1/trending/breakouts?country=US");
const { data } = await res.json();
console.log(data.items[0].title.name, data.items[0].why);import requests
r = requests.get("https://streamrank.io/v1/trending/breakouts", params={"country": "US"})
for item in r.json()["data"]["items"]:
print(item["position"], item["title"]["name"], item["why"])