# StreamRank API

StreamRank serves daily Top 10 charts for Netflix, Prime Video, Disney+, HBO Max, Hulu, Apple TV+, Paramount+, and Peacock.

Requests without a key run in the free scope: country=US; provider=netflix; current charts only. Send `Authorization: Bearer sk_live_…` or `sk_test_…` for a plan. Test keys never count toward quota.

This is Top 10 rankings plus one daily cross-service trending list, not a catalog of everything on a service. Charts and trend scores update once a day. Cache them.

Identifiers: provider ids are lowercase (`netflix`, not `Netflix`). Countries are ISO 3166-1 alpha-2 (`US`, not `USA`). Look up a title with `GET /v1/titles?q=` or `imdb_id` / `tmdb_id`. Title ids look like `ttl_…`.

- Base URL: https://streamrank.io/v1
- OpenAPI: https://streamrank.io/v1/openapi.json
- HTML docs: https://streamrank.io/docs
- This file: https://streamrank.io/docs.md

## Quickstart

```bash
curl "https://streamrank.io/v1/charts?provider=netflix&country=US&type=movie" \
  -H "Authorization: Bearer sk_live_…"
```

Response when `type=movie`:

```json
{
  "data": {
    "provider": "netflix",
    "country": "US",
    "type": "movie",
    "date": "2026-09-10",
    "updated_at": "2026-09-10T22:49:43.000Z",
    "rankings": [
      {
        "rank": 1,
        "previous_rank": 3,
        "rank_change": 2,
        "is_new": false,
        "days_in_top_10": 8,
        "peak_rank": 1,
        "first_charted_at": "2026-09-03",
        "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"
        }
      }
    ]
  }
}
```

Charts update once a day. Cache responses. We meter dataset access (countries, providers, history), not how often you fetch.

## 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.

```http
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
```

Requests 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.

| Plan | Countries | Providers | History | Trending list | Requests / mo |
| --- | --- | --- | --- | --- | --- |
| Free | US | netflix | Current only | Top 10 | 1,000 |
| Developer | All | All | 30 days | Top 100 | 50,000 |
| Pro | All | All | Full | Top 100 | 500,000 |
| Business | All | All | Full | Top 100 | 5,000,000 |

## Charts

<a id="charts"></a>

`GET /v1/charts`

The Top 10 for one provider in one country. Omit `type` to receive both the movie and TV charts in one response.

| Name | Type | Description |
| --- | --- | --- |
| `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. |

```http
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-01
```

| Field | Type | Description |
| --- | --- | --- |
| `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. |

Derived metrics come from our stored history, not from the upstream source.

## Titles

<a id="titles"></a>
<a id="title-search"></a>

`GET /v1/titles`

Returns a ranked list. Ambiguous names are not a 404. Then call GET /v1/titles/{id} for current positions. At least one of q, imdb_id, or tmdb_id is required.

| Name | Type | Description |
| --- | --- | --- |
| `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. |

```http
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
```


```json
{
  "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 id="title"></a>

`GET /v1/titles/{id}`

Accepts a title id (`ttl_…`), slug, IMDb id, or TMDB id. TMDB ids that match both a movie and a show should use GET /v1/titles?tmdb_id=&type= instead. Returns where it sits on every current chart plus lifetime stats.

| Name | Type | Description |
| --- | --- | --- |
| `id` | string | Title id (`ttl_…`), slug, IMDb id, or numeric TMDB id. |

```http
GET /v1/titles/ttl_5k2m9x1qzc7v
GET /v1/titles/the-whisper-man-2026
GET /v1/titles/tt11561116
```


<a id="title-rankings"></a>

`GET /v1/titles/{id}/rankings`

Newest first, clamped to your plan's history window.

| Name | Type | Description |
| --- | --- | --- |
| `id` | string | Title id (`ttl_…`) or slug. |
| `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. |

```http
GET /v1/titles/ttl_5k2m9x1qzc7v/rankings?country=US&provider=netflix
```


```json
{
  "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

<a id="trending"></a>

`GET /v1/trending`

Movement inside one provider's charts in one country: `biggest_risers`, `new_entries`, and `longest_streaks`. It compares a chart with itself, so a #1 on Apple TV+ and a #5 on Netflix are not comparable. For one list that ranks titles across every service, use GET /v1/trending/titles.

| Name | Type | Description |
| --- | --- | --- |
| `provider` | string | Required. |
| `country` | string | Required. ISO 3166-1 alpha-2. |
| `limit` | 1–20 | Optional. Items per bucket, default 5. |

```http
GET /v1/trending?provider=netflix&country=US
```


### Trending titles

<a id="trending-titles"></a>

`GET /v1/trending/titles`

Titles ranked across every service by a single trend score, 0–100. Three categories feed the score: `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 scope and are summed on the global list), 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.

| Name | Type | Description |
| --- | --- | --- |
| `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. |

```http
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
```


```json
{
  "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
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `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 score is daily. Charts and attention use the newest data on or before the list date. First-party demand counts cover the last complete day, so they sit one day behind. Attention is discounted when it is flat: a title that draws the same attention every day keeps half its level, a spike keeps all of it. Titles seen only by attention rank after every title with chart or demand presence unless attention is spiking. `rising` needs growth and a meaningful level, so one new chart entry on its own does not qualify.

Scope: omit `country` 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. Scores are only comparable inside one scope and one day.

<a id="title-trend"></a>

`GET /v1/titles/{id}/trend`

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.

| Name | Type | Description |
| --- | --- | --- |
| `id` | string | Title id (`ttl_…`), slug, IMDb id, or numeric TMDB id. |
| `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. |

```http
GET /v1/titles/ttl_5k2m9x1qzc7v/trend
GET /v1/titles/the-whisper-man-2026/trend?country=US&limit=30
```


```json
{
  "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
  }
}
```

## Countries

<a id="countries"></a>

`GET /v1/countries`

Every country with chart data and the providers available in each, plus whether your plan includes it.

```http
GET /v1/countries
```


```json
{
  "data": [
    {
      "code": "US",
      "name": "United States",
      "providers": [
        "netflix",
        "prime",
        "disney",
        "max",
        "hulu",
        "apple",
        "paramount",
        "peacock"
      ],
      "included_in_plan": true
    }
  ]
}
```

## Providers

<a id="providers"></a>

`GET /v1/providers`

Every provider we cover, the countries that have data, and whether your plan includes it.

```http
GET /v1/providers
```


```json
{
  "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
    }
  ]
}
```

## Errors

<a id="errors"></a>

Errors are JSON with a stable `code` and a human-readable `message`.

```json
{
  "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"
    }
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 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`. Every error includes `details.docs_url` and, when useful, `details.hint` with the next legal call.

## SDKs

The API is plain HTTP. Official SDKs are not published yet.

```javascript
const res = await fetch(
  "https://streamrank.io/v1/charts?provider=netflix&country=US&type=movie",
  { headers: { Authorization: `Bearer ${process.env.STREAMRANK_KEY}` } }
);
const { data } = await res.json();
console.log(data.rankings[0].title.name);
```

```python
import os, requests

r = requests.get(
    "https://streamrank.io/v1/charts",
    params={"provider": "netflix", "country": "US", "type": "movie"},
    headers={"Authorization": f"Bearer {os.environ['STREAMRANK_KEY']}"},
)
for item in r.json()["data"]["rankings"]:
    print(item["rank"], item["title"]["name"])
```
