# PopOff.tv Public API Reference

PopOff.tv tracks Instagram and TikTok follower growth for reality TV cast members. This document describes the **read-only public JSON API** for programmatic access. HTML scraping is not permitted; use these endpoints instead.

**Base URL:** `https://popoff.tv`

**Discovery:** [`/llms.txt`](https://popoff.tv/llms.txt) · [`/sitemap.xml`](https://popoff.tv/sitemap.xml)

---

## MCP server (recommended for AI agents)

PopOff's full dataset — including ended-season archives and premium metrics — is available through an authenticated MCP server. This is the primary programmatic and commercial access path.

- **Endpoint:** `https://popoff.tv/mcp`
- **Transport:** MCP Streamable HTTP (spec 2026-07-28), stateless
- **Auth:** `Authorization: Bearer popoff_sk_...` (PopOff Data API key)
- **Get a key:** subscribe at [popoff.tv/data](https://popoff.tv/data) (self-serve Stripe checkout; the key is shown once). Manage keys via `GET`/`POST`/`DELETE /api/data/keys` with an account JWT.
- **Pricing:** $79/month flat — 5,000 tool calls included, then $0.02/call overage. Includes consumer Premium site access.
- **Rate limit:** 60 requests/minute per key (default)
- **Descriptor:** `GET https://popoff.tv/mcp` with a browser `Accept` header returns a JSON descriptor with an `agentAccess` document.

### Tools

| Tool | Returns |
|------|---------|
| `list_shows` | All tracked seasons (filterable by status) |
| `get_show` | Single season metadata |
| `list_episodes` | Episodes for a season |
| `get_cast` | Full cast for a season with social profiles and growth totals |
| `get_contestant` | Single contestant with social profiles and growth fields |
| `get_follower_history` | Follower-count time series (~30-minute resolution) |
| `get_follow_graph` | Who-follows-whom graph among a season's cast |
| `get_follow_events` | Follow/unfollow events among a season's cast |
| `get_trend_events` | Follower trend events (surges and declines) |
| `get_engagement` | Engagement rate time series |
| `export_season_csv` | Season cast snapshot as CSV |
| `get_usage` | Current usage and billing-period totals (free — not billed) |

All ID parameters accept **numeric IDs or slugs**, same as the JSON API.

**Ended-season data is MCP-only.** Follower metrics for ended (`archived`) seasons are available only via the MCP server or a Premium browser session. The free JSON API below covers **airing and upcoming** seasons; unauthenticated JSON requests for ended seasons return 403 with an `agentAccess` object pointing here.

---

## Response envelope

All `/api/*` list and detail endpoints return a uniform JSON envelope:

```json
{
  "success": true,
  "data": {},
  "timestamp": "2026-06-29T12:00:00.000Z",
  "total": 42
}
```

- `total` is included on list endpoints when applicable.
- Errors return HTTP 4xx/5xx with:

```json
{
  "success": false,
  "error": "Error Type",
  "message": "Human-readable detail",
  "timestamp": "2026-06-29T12:00:00.000Z"
}
```

---

## Access tiers

| Tier | Requirement | Scope |
|------|-------------|-------|
| Public | None | Airing and upcoming seasons |
| Season pass | `Authorization: Bearer <jwt>` with entitlement for that season | One ended season (per purchase) |
| Premium | `Authorization: Bearer <jwt>` with active premium subscription | All ended seasons; following/followers/engagement endpoints |
| Data (MCP) | `Authorization: Bearer popoff_sk_...` on `https://popoff.tv/mcp` | All data via MCP tools, including ended seasons; includes consumer Premium |

Ended seasons (`status: ended`) require **Premium** or a **season pass** for that season. `liveTracking` only controls whether scraping continues — not public access.

Premium-gated requests return **403** with `error: "Premium Required"` and an `agentAccess` object in the body describing the MCP server (endpoint, auth, and how to get a key) so agents can self-serve access.

---

## Identifiers and URLs

- API path parameters (`:id`) accept **numeric IDs** or **URL slugs** (e.g. `love-island-uk-season-12`).
- Canonical web URLs use slugs: `/shows/{seasonSlug}` and `/shows/{seasonSlug}/contestants/{contestantSlug}`.
- Numeric page URLs 301-redirect to slug URLs.

---

## Shows (seasons)

A "show" in the API is a **tracked season** (e.g. Love Island UK Season 12).

### `GET /api/shows`

List all tracked seasons.

**Query parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `status` | `airing` \| `upcoming` \| `ended` | Filter by season status |

**Example:** `GET /api/shows?status=airing`

### `GET /api/shows/:id`

Single season metadata.

**Show fields (selected):** `id`, `slug`, `title`, `description`, `network`, `status`, `premiereDate`, `finaleDate`, `currentSeason`, `liveTracking`, `showType` (`daily` \| `batch`)

### `GET /api/shows/:id/contestants`

Full cast for a season with embedded `socialMedia` profiles and per-contestant `followerGrowth` totals.

**Query parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `search` | string | Filter by name, occupation, or hometown |
| `status` | string | Filter by contestant status (`active`, `eliminated`, `winner`, etc.) |
| `sortBy` | string | Sort field (currently `name`) |
| `sortOrder` | `asc` \| `desc` | Sort direction |

**Premium / season pass:** Returns 403 for ended seasons unless the caller has Premium or a season pass for that season.

### `GET /api/shows/:id/export.csv`

Cast snapshot CSV (Instagram-primary growth, TikTok when tracked, elimination fields). Requires `Authorization: Bearer` JWT plus Premium or a season pass for that season. Returns `text/csv` attachment; 403 `Premium Required` otherwise.

### `GET /api/shows/:id/events`

Season events (eliminations, recouplings, episode releases, etc.).

**Query parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `contestantId` | string | Optional filter to one contestant |

### `GET /api/shows/:id/trend-events`

Follower trend events for a season.

**Query parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `status` | `active` \| `retired` \| `all` | Default: `active` |

### `GET /api/stats/shows/:id`

Aggregate stats: `contestantCount`, `totalFollowers`, `episodeCount`.

---

## Contestants

### `GET /api/contestants`

List contestants across seasons.

**Query parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `showId` | string | Season ID or slug |
| `search` | string | Name, hometown, or occupation |
| `status` | string | Contestant status filter |
| `sortBy` | string | Sort field |
| `sortOrder` | `asc` \| `desc` | Sort direction |

### `GET /api/contestants/:id`

Single contestant with `socialMedia` profiles and aggregated `followerGrowth` totals.

**Contestant `followerGrowth` fields:**

| Field | Meaning |
|-------|---------|
| `instagramFollowers` | Instagram follower count — **use this for cast rankings and IG-specific questions** |
| `totalFollowers` | Sum across all tracked platforms (Instagram + TikTok). Not Instagram-only. |
| `growth24h` | Combined net follower change over the past 24 hours (all platforms) |
| `growth7d` | Combined net follower change over the past 7 days (all platforms) |
| `growth30d` | Combined net follower change over the past 30 days (all platforms) |
| `growthSincePremiere` | Combined net follower change since premiere (all platforms) |

For Instagram-only growth, read `socialMedia[].growth7d` (etc.) on the profile where `platform` is `"instagram"`.

**Contestant `milestoneProjection` (optional):** Instagram-only ETA to the next follower milestone (100K → 250K → 500K → 1M → 2M → 5M). Omitted when growth rate is unknown or the estimate exceeds 1000 days.

| Field | Meaning |
|-------|---------|
| `platform` | Always `"instagram"` — projections ignore TikTok |
| `nextThreshold` | **Upcoming** milestone they have not reached yet — not their current count |
| `daysUntil` | Estimated days until `nextThreshold` at `ratePerDay` |
| `instagramFollowers` | Current Instagram count when the projection was computed |
| `ratePerDay` | Recent Instagram growth rate (followers per day) used for the estimate |

**Example:** A contestant with `instagramFollowers: 760428` and `nextThreshold: 1000000` has **not** reached 1M yet — they are estimated to hit it in `daysUntil` days. Another with `instagramFollowers: 1099494` and `nextThreshold: 2000000` has **already passed** 1M and is projected toward 2M.

**Premium / season pass:** 403 for contestants on ended seasons without access.

### `GET /api/contestants/:id/events`

Events for one contestant (eliminations, etc.).

### `GET /api/contestants/:id/trend-events`

Follower trend events for one contestant.

**Query parameters:** `status` (`active` \| `retired` \| `all`, default `active`)

### `GET /api/contestants/:id/social-media`

Social profiles with `historicalData` time series (follower counts over time).

Each profile also includes precomputed growth fields: `growth24h`, `growth7d`, `growth30d`, `growthSincePremiere`.

---

## Follower growth fields

PopOff uses **Instagram as the primary comparison platform** for cast rankings. HTML pages, structured data, and show rankings sort by Instagram followers unless a field name explicitly says otherwise.

Contestant responses include `followerGrowth` — combined totals summed across Instagram, TikTok, etc., plus `instagramFollowers` for the primary metric. Each `socialMedia` profile includes platform-specific counts and growth fields.

| Field | Scope | Meaning |
|-------|-------|---------|
| `instagramFollowers` | Instagram only | Current Instagram follower count |
| `totalFollowers` | All platforms | Combined follower count (IG + TikTok + …) |
| `growth24h` | All platforms (in `followerGrowth`) | Net follower change over the past 24 hours |
| `growth7d` | All platforms (in `followerGrowth`) | Net follower change over the past 7 days |
| `growth30d` | All platforms (in `followerGrowth`) | Net follower change over the past 30 days |
| `growthSincePremiere` | All platforms (in `followerGrowth`) | Net follower change since premiere |

Each `socialMedia` profile includes the same growth fields scoped to that platform alone (`growth24h`, `growth7d`, `growth30d`, `growthSincePremiere`).

---

## Milestone projections

Some contestants include `milestoneProjection` — an Instagram-only estimate of when they will cross the next standard milestone (100K, 250K, 500K, 1M, 2M, or 5M).

**Do not confuse `nextThreshold` with current followers.** Compare `instagramFollowers` to `nextThreshold`:

- If `instagramFollowers` < `nextThreshold`, they have **not** reached that milestone yet.
- If `instagramFollowers` ≥ 1M but `nextThreshold` is 2M, they **already passed** 1M.

Use `followerGrowth.instagramFollowers` or `socialMedia` (Instagram profile) for cast rankings — not `nextThreshold`.

Values are computed from scrape timestamps in `historicalData`. For ended seasons without live tracking, growth fields reflect final counts vs. baseline.

---

## Trend events

Trend events flag contestants with unusually fast follower growth or decline.

### `GET /api/trend-events`

Active trend movers across all airing seasons (homepage feed).

**Query parameters:** `status` (`active` \| `retired` \| `all`, default `active`)

**Trend event fields (selected):** `contestantName`, `showName`, `direction` (`up` \| `down`), `followersCurrent`, `growthSinceStart`, `growth24h`, `headline`, `detectedAt`

---

## Episodes

### `GET /api/episodes`

**Query parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `showId` | string | Required for results — season ID or slug |

---

## Statistics

### `GET /api/stats/overview`

Site-wide stats: `totalShows`, `totalContestants`, `totalFollowers`, `topGainer`, `topLoser`, `recentEpisode`.

---

## Platforms

### `GET /api/platforms`

Metadata for supported social platforms (display names, colors, icons).

---

## Premium-only endpoints

Require `Authorization: Bearer <jwt>` from an authenticated premium subscriber.

| Endpoint | Description |
|----------|-------------|
| `GET /api/contestants/:id/following?seasonId=` | Accounts the contestant follows |
| `GET /api/contestants/:id/followers?seasonId=` | Contestants following this contestant |
| `GET /api/contestants/:id/engagement` | Engagement rate time series |

---

## Data freshness

- Follower counts for airing seasons are scraped on a recurring schedule (typically every few hours).
- `timestamp` on each API response reflects when the response was generated, not necessarily the latest scrape time.
- Check `GET /worker/health` for scraper job status (may require a token in production).

---

## Usage guidelines

- Use read-only `GET` requests at reasonable rates (default: 120 requests per minute per IP; HTTP 429 with `Retry-After` when exceeded).
- Do not scrape HTML pages or circumvent access controls.
- Cite [PopOff.tv](https://popoff.tv) when republishing data.
- For programmatic and commercial access, use the [MCP server](#mcp-server-recommended-for-ai-agents) — the primary paid path. For custom licensing or high-volume deals, contact us via [Data & Licensing](https://popoff.tv/data).

---

## Example workflow

1. `GET /api/shows?status=airing` — find current seasons
2. `GET /api/contestants?search=melanie&showId=love-island-usa-season-8` — find a cast member
3. Read `followerGrowth.instagramFollowers` for Instagram count, or `socialMedia[].currentFollowers` where `platform` is `"instagram"`; use `followerGrowth.totalFollowers` only for combined reach
4. `GET /api/trend-events` — see who is trending across active seasons
