---
title: Linear TV REST API
menu_group: REST API
menu_order: 45
tab: Linear TV
tab_order: 40
requires_linear_tv: true
summary: Public channels, now playing, schedule, EPG, Live content, and admin scheduling endpoints for connected TV clients.
---

# Linear TV REST API

Base namespace: `/wp-json/mediablaster/v3`

Requires **Enable Live Channels** in General Settings (`wpstv_is_linear_channels_enabled()`).

## Discovery

`GET /mediablaster/v3` includes `features.linear_tv: true` and links to `live`, `channels`, and `epg` when enabled.

## Public — Live content

- `GET /live` — Live stream collection (standard content API shape with `live` object)
- `GET /live/{id}` — Single Live stream

## Public — Channels and EPG

- `GET /channels` — Enabled published channels
- `GET /channels/{id}` — Channel detail
- `GET /channels/{id}/now` — Current and next program with playback offsets
- `GET /channels/{id}/schedule?start=&end=` — EPG window (max 7 days)
- `GET /epg?start=&end=&channel_ids=` — Multi-channel guide (max 7 days)

Compiled airings and API timestamps are **UTC** (`...Z` or MySQL UTC stored values). Channel `timezone` is metadata for clients and admin.

### Enabled / EPG visibility meta

Channel CMB2 checkboxes store **`on`** when checked (not `1`). Listing and EPG queries use:

- `MediaBlaster_Linear_Channel_Service::enabled_meta_query()` — `_mediablaster_channel_enabled` IN (`on`, `1`)
- `MediaBlaster_Linear_Channel_Service::epg_visible_meta_query()` — `_mediablaster_channel_epg_visible` IN (`on`, `1`)

If `GET /channels` returns empty `items` despite published channels with Enabled checked, a query that only matches `'1'` is the usual cause.

## Deep linking

- `GET /deep-link/{id}?mediaType=liveFeed` — Live posts and Channels (`behavior: channel` for Channels)

## App config

`GET /config?platform=roku` includes `linearTv` URLs and sets `enableLive` / `enableEPG` / `features.liveTv` / `features.epg` when the module is enabled.

## Admin — Playlist and library (authenticated)

Requires `edit_posts` (library) or `edit_post` on the playlist/channel.

| Method | Route | Purpose |
|--------|-------|---------|
| GET | `/linear/library?type=&search=&page=&per_page=` | Content library for Playlist Builder |
| GET | `/linear/playlists/{id}/items` | Playlist items + summary |
| PUT/PATCH | `/linear/playlists/{id}/items` | Replace playlist items |

## Admin — Schedule blocks (authenticated)

Requires `edit_post` on the channel.

| Method | Route | Purpose |
|--------|-------|---------|
| GET | `/linear/channels/{id}/schedule-blocks?start=&end=` | Blocks in UTC range (scheduler grid) |
| POST | `/linear/channels/{id}/schedule-blocks` | Create block |
| PATCH | `/linear/channels/{id}/schedule-blocks/{block_id}` | Update block |
| DELETE | `/linear/channels/{id}/schedule-blocks/{block_id}` | Delete block |
| POST | `/linear/channels/{id}/compile` | Run schedule compiler for channel |
| POST | `/linear/channels/{id}/duplicate-week` | Copy one-time blocks to another week |

### Schedule block payload (create/update)

```json
{
  "playlist_id": 123,
  "start_at_utc": "2026-07-14 12:00:00",
  "end_at_utc": "2026-07-14 13:00:00",
  "timezone": "UTC",
  "recurrence": "none",
  "recurrence_until_utc": null,
  "loop_playlist": false,
  "enabled": true,
  "title_override": null,
  "settings": {
    "notes": "Optional block notes from the scheduler inspector"
  }
}
```

- `recurrence`: `"none"` or `"weekly"` (weekly requires `recurrence_until_utc`)
- `settings.notes` — optional; sanitized with `sanitize_textarea_field()`; no dedicated DB column
- `settings.recurrence_local` — optional `{ weekday, time, timezone }` written on weekly save for DST-safe expansion (backward compatible)
- Overlapping **enabled occurrences** (including expanded weekly series) return **409** `schedule_overlap` with `data.conflict` (`block_id`, `title`, `start_at_utc`, `end_at_utc`) when available; exact adjacency is allowed
- Create response: `{ "id": N, "block": { … } }`; update response: `{ "success": true, "block": { … } }`
- Invalid playlist returns **400** `invalid_playlist`
- PATCH from drag/resize/inspector should include existing `settings` and `title_override`. On update, the server merges existing settings so omitted keys are not wiped.

### Duplicate week

POST body:

```json
{
  "source_week_start": "2026-07-07 00:00:00",
  "dest_week_start": "2026-07-14 00:00:00",
  "confirm": true
}
```

Omit `confirm` to receive `{ preview, count }` without writing.

## Entitlements

When subscriptions are enabled, channel access can be gated via entitlements (`channels` post type added to allowlists). Premium Live/playlist items follow existing content access rules in compiled airings.

## Related

- [Linear TV Overview](linear-tv-overview.md)
- [Channel Scheduler](channel-scheduler.md)
