---
title: Content API and Fields
menu_group: REST API
menu_order: 10
tab: Content
tab_order: 10
summary: List, create, update, and delete MediaBlaster content with clean JSON field names.
---

# Content API and Fields

The content API manages Movies, Videos, Episodes, and Series using normalized JSON. Internal storage still uses `rovidx_smarttv_*` and `rovidx_smart_tv_*` meta keys; the REST layer maps those automatically.

## Endpoints

### Unified routes

| Method | Route | Description |
|--------|-------|-------------|
| GET | `/content` | Paginated collection |
| POST | `/content` | Create (requires `type` in body) |
| GET | `/content/{id}` | Single item |
| PATCH | `/content/{id}` | Partial update |
| DELETE | `/content/{id}` | Trash (or permanent with `?force=true`) |

### Type-specific aliases

Same behavior as `/content`, with `type` fixed by the URL:

| Type | List/create | Item |
|------|-------------|------|
| movies | `GET`, `POST` `/movies` | `GET`, `PATCH`, `DELETE` `/movies/{id}` |
| videos | `/videos` | `/videos/{id}` |
| episodes | `/episodes` | `/episodes/{id}` |
| series | `/series` | `/series/{id}` |

`POST /movies` creates a movie even if `type` is omitted from the JSON body.

If `{id}` exists but belongs to another post type, the API returns **404** `mediablaster_not_found`.

## List collection — query parameters

`GET /content` (or `GET /movies`, etc.)

| Parameter | Description |
|-----------|-------------|
| `type` | Filter: `movies`, `videos`, `episodes`, `series` |
| `status` | `publish`, `draft`, `pending`, `private`, or `any` (editors only) |
| `search` | WordPress search (`s`); for mixed video + podcast results use [GET /search](rest-api-search.md) |
| `genre` | Filter by genre meta slug |
| `category` | Filter by category `id` from `GET /categories` |
| `tag` | Post tag slug or name |
| `series_id` | Episodes in a series playlist (`rovidx_smarttv_playlist`) |
| `page` | Page number (default `1`) |
| `per_page` | Items per page (default `20`, max `100`) |
| `orderby` | `date`, `modified`, `title`, `menu_order`, `rand` |
| `order` | `ASC` or `DESC` |
| `include_locked` | `true` to include items hidden with “hide completely” access |

**Public default:** only `publish` status. Logged-in users with `edit_posts` may request drafts or `status=any`.

### Collection response

```json
{
  "items": [ { "...content object..." } ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total": 124,
    "total_pages": 7
  }
}
```

### Tag-scan pagination (Roku)

Roku Home and Movies screens fetch **full catalogs** (no `tag` filter), paginate with `page` and `per_page`, then group items client-side by `taxonomy.tags`.

| Endpoint | Typical `per_page` | Default sort |
|----------|-------------------|--------------|
| `GET /movies` | `50` | `orderby=date`, `order=DESC` |
| `GET /series` | `50` | `orderby=date`, `order=DESC` |
| `GET /videos` | `50` | `orderby=date`, `order=DESC` |
| `GET /content` | `20` | `orderby=date`, `order=DESC` |
| `GET /episodes` | `50` | `orderby=date`, `order=DESC` |

Every list response includes `pagination` with `page`, `per_page`, `total`, and `total_pages`. An empty catalog returns `items: []` and `total_pages: 0`.

**Tag rows:** assign WordPress **post tags** on movies, series, or videos. The API exposes tag **display names** in `taxonomy.tags` (for example `["Featured", "Action"]`). Tag `Featured` drives the Home hero carousel (up to five items); on Movies, Featured appears as the first row.

**Images:** card and hero art use `thumbnail` plus the nested `images` object (for example `images.landscape.card`, `images.landscape.backdrop`). There are no separate top-level `poster` or `backdrop` fields.

#### Validation checklist

- Response JSON has `items` (array) and `pagination` (object)
- `pagination.page` matches the requested `page`
- `pagination.per_page` matches the effective page size (capped at 100)
- `pagination.total_pages === ceil(total / per_page)` (or `0` when `total` is `0`)
- Each item has `id`, `title`, and `taxonomy.tags` (array, may be empty)
- `per_page=50` and `per_page=100` succeed without error
- Page beyond the last returns `200` with `items: []`

See also [Categories API](rest-api-categories.md).

## Create and update

### Defaults

- New content defaults to **`draft`**.
- Sending `"status": "publish"` requires `publish_posts` (or equivalent); otherwise **403**.
- **PATCH** only updates fields present in the body; omitted nested keys are not cleared.

### Minimal create example

`POST /wp-json/mediablaster/v3/movies`

```json
{
  "title": "REST Test Movie",
  "description": "Long description.",
  "excerpt": "Short description.",
  "media": {
    "url": "https://cdn.example.com/video.m3u8",
    "duration": 3600,
    "format": "HLS",
    "quality": "FHD"
  },
  "taxonomy": {
    "genres": ["documentary"],
    "rating": "PG",
    "tags": ["History", "Featured"]
  },
  "vimeo": {
    "source_url": "https://vimeo.com/123456789"
  }
}
```

### Partial update example

`PATCH /wp-json/mediablaster/v3/movies/123`

```json
{
  "title": "Updated Title",
  "media": {
    "quality": "UHD"
  }
}
```

Only `title` and `media.quality` change; existing URL and duration remain.

## Delete

`DELETE /content/{id}`

- Default: move to **trash** (`trashed: true`).
- `DELETE /content/{id}?force=true` — permanent delete (requires elevated delete capability).

Response includes `deleted`, `trashed`, and `previous` (serialized snapshot).

## Content object (response shape)

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | WordPress post ID |
| `type` | string | Post type slug |
| `status` | string | `draft`, `publish`, `pending`, `private` |
| `slug` | string | Post name |
| `title` | string | Post title |
| `description` | string | `post_content` |
| `excerpt` | string | `post_excerpt` |
| `created_at` | string | ISO 8601 (GMT) |
| `updated_at` | string | ISO 8601 (GMT) |
| `thumbnail` | object | `{ "id", "url" }` — legacy; see also `images` |
| `images` | object | Roku-sized artwork variants (see below) |
| `media` | object | See below |
| `taxonomy` | object | `tags`, `genres`, `rating` |
| `series` | object | Series linkage (episodes) or series meta |
| `captions` | array | Caption tracks |
| `trickplay` | array | Trickplay/BIF files |
| `ads` | object | Ad breaks |
| `availability` | object | Start/end dates |
| `access` | object | Subscription lock state |
| `vimeo` | object | `source_url` |
| `links` | object | `self`, `edit` (if allowed) |

### `images`

Pre-sized artwork for connected TV clients (Roku). Generated from a single attachment source (featured image for movies/videos/episodes; series artwork attachment when `series.thumbnail_id` is set). Each variant is hard center-cropped to exact pixel dimensions.

Omit individual role keys when the attachment or size cannot be resolved. Omit empty `landscape` / `portrait` buckets. URL-only series artwork (`series.thumbnail` without `thumbnail_id`) cannot produce sized variants — use legacy `series.thumbnail` or top-level `thumbnail` instead.

**Backward compatibility:** `thumbnail` is unchanged. Clients should prefer `images.{orientation}.{role}` when present.

```json
"images": {
  "landscape": {
    "card":     { "id": 101, "url": "https://…/image-200x112.jpg",   "width": 200,  "height": 112 },
    "hero":     { "id": 101, "url": "https://…/image-1184x360.jpg",  "width": 1184, "height": 360 },
    "backdrop": { "id": 101, "url": "https://…/image-1280x720.jpg",  "width": 1280, "height": 720 }
  },
  "portrait": {
    "card":     { "id": 101, "url": "https://…/image-200x300.jpg",  "width": 200,  "height": 300 },
    "details":  { "id": 101, "url": "https://…/image-300x450.jpg",  "width": 300,  "height": 450 },
    "vertical": { "id": 101, "url": "https://…/image-135x240.jpg",  "width": 135,  "height": 240 },
    "square":   { "id": 101, "url": "https://…/image-200x200.jpg",  "width": 200,  "height": 200 }
  }
}
```

| Role key | Orientation | Dimensions | Typical use |
|----------|-------------|------------|-------------|
| `card` | landscape | 200×112 | Row thumbnails, search, continue watching |
| `hero` | landscape | 1184×360 | Home hero carousel |
| `backdrop` | landscape | 1280×720 | Details / episode cinematic background |
| `card` | portrait | 200×300 | Portrait-mode row cards |
| `details` | portrait | 300×450 | Details screen poster |
| `vertical` | portrait | 135×240 | 9:16 shorts rows |
| `square` | portrait | 200×200 | Podcast tiles |

Each variant object includes `id` (attachment ID), `url`, `width`, and `height`.

Existing attachments receive new intermediate sizes on first REST read. For bulk regeneration on large libraries, run `wp media regenerate`.

Registered sizes are listed on **Settings → Media** under **MediaBlaster Image Sizes**, and appear in the Media Library attachment size dropdown.

### `media`

| Field | Allowed values | Notes |
|-------|----------------|-------|
| `url` | URI | Playback URL; `null` if user lacks access |
| `duration` | integer (seconds) | Accepts `hh:mm:ss` on write |
| `format` | `MP4`, `MOV`, `M4V`, `HLS`, `SMOOTH`, `DASH` | |
| `quality` | `SD`, `HD`, `FHD`, `UHD` | |

### `taxonomy`

| Field | Description |
|-------|-------------|
| `tags` | Post tag names |
| `genres` | Genre keys (multicheck values) |
| `rating` | Parental rating string |

### `series` (episodes)

| Field | Description |
|-------|-------------|
| `series_id` | Parent series post ID (derived from playlist membership) |
| `season_number` | Season number |
| `episode_number` | Episode number |

### `series` (series post type)

Additional fields on `type: series` responses: `type`, `release_date`, `playlist`, `thumbnail`, `thumbnail_id`, `genres`.

### `captions[]`

| Field | Description |
|-------|-------------|
| `url` | Caption file URL |
| `language` | Language code |
| `type` | e.g. `CLOSED_CAPTION`, `SUBTITLE` |

### `trickplay[]`

| Field | Description |
|-------|-------------|
| `url` | BIF or trickplay file URL |
| `quality` | Quality label |

### `ads`

| Field | Description |
|-------|-------------|
| `ad_breaks` | Array of break positions (seconds) |
| `custom_midroll_timer` | Custom midroll offset or `null` |

### `availability`

| Field | Description |
|-------|-------------|
| `start_date` | Availability start or `null` |
| `end_date` | Availability end or `null` |

### `access`

Present when subscriptions are enabled:

| Field | Description |
|-------|-------------|
| `is_locked` | Premium/locked for current user |
| `user_can_access` | Whether playback URL may be shown |
| `required_tier` | Tier object or `null` |
| `required_access_group` | Access group object or `null` |

### `vimeo`

| Field | Description |
|-------|-------------|
| `source_url` | Vimeo page URL (`rovidx_smart_tv_vm_pro_url`) |

### `links`

| Field | Description |
|-------|-------------|
| `self` | REST URL for this item |
| `edit` | wp-admin edit link (only if user can edit) |

## Request body field reference

Use these keys in **POST** and **PATCH** bodies (not raw meta names).

### Top-level

| API field | Required | Notes |
|-----------|----------|-------|
| `type` | On `POST /content` | One of four content types |
| `status` | No | Default `draft` |
| `title` | On create | |
| `description` | No | HTML allowed (sanitized) |
| `excerpt` | No | |
| `slug` | No | Post name |

### `media` object

| API field | Internal meta key |
|-----------|-------------------|
| `url` | `rovidx_smarttv_URL` |
| `duration` | `rovidx_smarttv_Duration` |
| `format` | `rovidx_smarttv_format` |
| `quality` | `rovidx_smarttv_quality` |

### `taxonomy` object

| API field | Storage |
|-----------|---------|
| `genres` | `rovidx_smarttv_genres` |
| `rating` | `rovidx_smarttv_rating` |
| `tags` | WordPress post tags |

### `series` object (episodes)

| API field | Internal meta key |
|-----------|-------------------|
| `season_number` | `rovidx_smarttv_se_no` |
| `episode_number` | `rovidx_smarttv_ep_no` |

### `series` object (series CPT)

| API field | Internal meta key |
|-----------|-------------------|
| `type` | `rovidx_smarttv_series_type` |
| `release_date` | `rovidx_smarttv_series_release_date` |
| `playlist` | `rovidx_smarttv_playlist` (array of post IDs) |
| `thumbnail` | `rovidx_smarttv_series_thumb` |
| `thumbnail_id` | `rovidx_smarttv_series_thumb_id` |
| `genres` | `rovidx_smarttv_series_genres` |

### `captions[]`

| API field | Internal key |
|-----------|--------------|
| `url` | `rovidx_smarttv_cc_uri` |
| `language` | `rovidx_smarttv_cc_lang` |
| `type` | `rovidx_smarttv_cc_type` |

Stored as CMB2 group `rovidx_smarttv_cc`.

### `trickplay[]`

| API field | Internal key |
|-----------|--------------|
| `url` | `rovidx_smarttv_bif_uri` |
| `quality` | `rovidx_smarttv_bif_def` |

Stored as group `rovidx_smarttv_bif`.

### `ads`

| API field | Internal meta key |
|-----------|-------------------|
| `ad_breaks` | `rovidx_smarttv_ad_breaks` |
| `custom_midroll_timer` | `rovidx_smarttv_custom_midroll_timer` |

### `availability`

| API field | Internal meta key |
|-----------|-------------------|
| `start_date` | `rovidx_smarttv_start_date` |
| `end_date` | `rovidx_smarttv_end_date` |

### `vimeo`

| API field | Internal meta key |
|-----------|-------------------|
| `source_url` | `rovidx_smart_tv_vm_pro_url` |

### `thumbnail`

| API field | Action |
|-----------|--------|
| `id` | Sets featured image attachment ID |

## Related guides

- [REST API Overview](rest-api-overview.md)
- [Video Data metabox](metabox-video-data.md) (admin UI for the same fields)
- [Vimeo REST API](rest-api-vimeo.md)
- [Metabox Subscription Access](metabox-subscription-access.md)
