Skip to main content

Episodes REST API

Endpoints for creating, reading, updating, and deleting episodes; managing episode availability; and managing per-show episode type definitions. All endpoints require manage_options and a valid X-WP-Nonce header unless noted.


Episodes

GET /benecaster/v1/shows/{id}/episodes

List episodes for a show. Supports filtering, sorting, and pagination.

Parameters:

Param Type Default Description
page int 1 Page number
per_page int 20 Items per page (max 100)
status string any publish, draft, scheduled, any
orderby string date date, episode_number, title
order string DESC ASC or DESC
search string Search by title or description
episode_type string Filter by type slug (e.g. full, trailer, bonus)
imported bool true to return only feed-sync imports

Response:

{
  "items": [
    {
      "id": 201,
      "show_id": 45,
      "title": "Ep 84: Interview with Sarah Chen",
      "episode_number": 84,
      "season_number": null,
      "episode_type": "full",
      "status": "publish",
      "post_date": "2026-11-03T09:00:00",
      "duration": "47:23",
      "file_size": 44368896,
      "audio_url": "https://buzzsprout.com/ep84.mp3",
      "artwork_url": "https://...",
      "category_ids": [1, 3],
      "availability": {
        "premium": "2026-11-03T09:00:00",
        "basic": "2026-11-10T09:00:00",
        "free": "2026-11-17T09:00:00",
        "public": null
      },
      "download_count": 342,
      "is_imported": false,
      "created_at": "2026-11-01T14:00:00",
      "updated_at": "2026-11-03T09:00:00"
    }
  ],
  "total": 83,
  "pages": 5,
  "per_page": 20,
  "page": 1
}

POST /benecaster/v1/episodes

Create a new episode.

Body:

{
  "show_id": 45,
  "title": "Ep 84: Interview with Sarah Chen",
  "description_rss": "This week I chat with Sarah Chen about...",
  "content": "<p>Full show notes here...</p>",
  "episode_number": 84,
  "season_number": null,
  "episode_type": "full",
  "explicit": null,
  "author": null,
  "audio_url": "https://buzzsprout.com/ep84.mp3",
  "video_url": null,
  "embed_audio": null,
  "embed_video": null,
  "media_type": "audio",
  "duration": "47:23",
  "file_size": 44368896,
  "mime_type": "audio/mpeg",
  "artwork_url": null,
  "web_visible_datetime": "2026-11-03T09:00:00",
  "availability": {
    "premium": "2026-11-03T09:00:00",
    "basic": "2026-11-10T09:00:00",
    "free": "2026-11-17T09:00:00",
    "public": null
  },
  "visibility_mode": "inherit",
  "tier_visibility": null,
  "category_ids": [1, 3],
  "references": [],
  "guests": [],
  "custom_fields": [],
  "external_links": [],
  "post_status": "draft"
}

Response: 201 Created with the full episode object.

Field notes:

Field Notes
description_rss Plain text or basic HTML — placed in the RSS feed <description>
content Full show notes; rendered on the episode page
episode_type Must be a slug defined for this show via POST /shows/{id}/episode-types
visibility_mode inherit (use show defaults), custom (use tier_visibility), or public
tier_visibility Object of tier slugs to datetime strings; used when visibility_mode is custom
category_ids Array of episode category IDs for this show
post_status draft or publish

GET /benecaster/v1/episodes/{id}

Get a single episode with all metadata.

Response: Full episode object — same shape as items in the list response plus computed fields: feed_urls, source_guid (for imported episodes), is_imported.


PUT /benecaster/v1/episodes/{id}

Update an episode. All fields optional — only provided fields are updated.

Response: Updated episode object.


DELETE /benecaster/v1/episodes/{id}

Delete an episode and all associated availability records.

Response: 200 OK with { "deleted": true }


POST /benecaster/v1/episodes/{id}/detect-metadata

Detect audio duration and file size from the episode’s audio URL. Makes a HEAD request to the URL and reads Content-Length and media duration. Premium only.

Response:

{
  "duration": "47:23",
  "duration_seconds": 2843,
  "file_size": 44368896,
  "mime_type": "audio/mpeg",
  "detected_at": "2026-11-01T14:00:00"
}

Errors:

Code HTTP Condition
detection_failed 400 URL unreachable or doesn’t support HEAD requests

Availability

GET /benecaster/v1/episodes/{id}/availability

Get availability datetimes for all configured tiers.

Response:

{
  "episode_id": 201,
  "inherits_show_defaults": true,
  "availability": {
    "premium": "2026-11-03T09:00:00",
    "basic": "2026-11-10T09:00:00",
    "free": "2026-11-17T09:00:00",
    "public": null
  },
  "next_unlock": {
    "tier": "basic",
    "datetime": "2026-11-10T09:00:00",
    "in_seconds": 604800
  }
}

null for a tier means that tier never gets access (or has no windowing rule set). next_unlock is null when no future unlock is pending.


PUT /benecaster/v1/episodes/{id}/availability

Update availability datetimes for an episode.

Body:

{
  "availability": {
    "premium": "2026-11-03T09:00:00",
    "basic": "2026-11-10T09:00:00",
    "free": "2026-11-17T09:00:00",
    "public": null
  },
  "inherit_show_defaults": false
}

Fires benecaster_availability_set. Invalidates the feed cache for affected show/tiers.


Member Thanks

GET /benecaster/v1/episodes/{id}/member-thanks

Returns a list of members to thank for the episode, using the configured query strategy.

Parameters:

Param Type Default Description
query_type string random_cycling Query strategy. Built-in: new_since_last_episode, random_cycling, milestone. Additional types via benecaster_member_thanks_query_types
count int 5 Number of members to return. 0 returns all results
tier string Filter results to a specific tier slug

Response:

{
  "episode_id": 201,
  "show_id": 45,
  "query_type": "random_cycling",
  "is_accepted_list": false,
  "members": [
    {
      "user_id": 1042,
      "display_name": "Sarah Chen",
      "tier_slug": "premium",
      "joined_at": "2026-09-14T11:32:00"
    }
  ]
}

is_accepted_list is true when the Podcast Workflow add-on is active and a list has been accepted for this episode via the Episode Workbench — in that case the accepted list is returned regardless of query_type and count.

Errors:

Code HTTP Condition
rest_not_found 404 Episode does not exist
invalid_query_type 400 query_type is not a registered strategy

Episode Types

Episode types are per-show labels that map to iTunes episode types (full, trailer, bonus). The default types (Full Episode, Trailer, Bonus, Sponsored) are created automatically for every new show.

GET /benecaster/v1/shows/{id}/episode-types

Returns all episode type definitions for a show.

Response:

[
  { "slug": "full", "label": "Full Episode", "itunes_type": "full", "sort_order": 0, "is_default": true, "episode_count": 82 },
  { "slug": "trailer", "label": "Trailer", "itunes_type": "trailer", "sort_order": 1, "is_default": true, "episode_count": 1 },
  { "slug": "bonus", "label": "Bonus", "itunes_type": "bonus", "sort_order": 2, "is_default": true, "episode_count": 0 },
  { "slug": "sponsored", "label": "Sponsored", "itunes_type": "full", "sort_order": 3, "is_default": true, "episode_count": 0 }
]

episode_count is used by the episode editor delete/reassign modal to warn when deleting a type that has episodes.


POST /benecaster/v1/shows/{id}/episode-types

Creates a new episode type for a show.

Body:

{
  "label": "Sponsored Bonus",
  "itunes_type": "full"
}

slug is auto-derived server-side from label. Made unique within the show by appending -2, -3, etc. if needed.

Response: 201 Created with the type object.

Errors:

Code HTTP Condition
invalid_param 400 itunes_type not one of full, trailer, bonus
invalid_param 400 label empty or exceeds 255 characters

PUT /benecaster/v1/shows/{id}/episode-types/{slug}

Updates the label and/or iTunes mapping of an existing type. Slug is immutable — episodes store the slug in _benecaster_episode_type and renaming it would orphan existing episodes.

Body: label and/or itunes_type — all fields optional.

Response: Updated type object.


DELETE /benecaster/v1/shows/{id}/episode-types/{slug}

Deletes an episode type. All episodes using this type are bulk-reassigned to another type in the same transaction.

Required query param: reassign_to — slug of the type to reassign to.

Response:

{
  "deleted": "sponsored",
  "reassigned_count": 14
}

Errors:

Code HTTP Condition
missing_reassign_to 400 reassign_to query param absent
invalid_reassign_to 400 reassign_to slug doesn’t exist on this show
reassign_to_self 400 reassign_to equals the slug being deleted
last_type 409 This is the last remaining type — shows must always have at least one
rest_not_found 404 Slug doesn’t exist on this show

Example:

DELETE /wp-json/benecaster/v1/shows/45/episode-types/sponsored?reassign_to=full

Feed Sync (Recurring)

POST /benecaster/v1/shows/{id}/sync

Trigger a manual feed sync — imports only new episodes not yet in WordPress (episodes whose GUID is not already stored). Premium only.

Body:

{
  "source_feed_url": "https://feeds.buzzsprout.com/12345.rss"
}

Response:

{
  "episodes_found": 86,
  "episodes_new": 3,
  "drafts_created": 3,
  "draft_ids": [201, 202, 203],
  "log_id": 47
}

For one-time bulk import of all episodes during setup, use POST /shows/{id}/import (see Setup Wizard REST API) instead.


Filters

Filter Where it fires
benecaster_episode_auto_number Filters the auto-assigned episode number on first publish
benecaster_episode_itunes_type Filters the resolved iTunes episode type during feed compilation
benecaster_episode_is_accessible Fires inside benecaster_user_can_access_episode() — override episode access for any tier
benecaster_episode_references Filters the references array in REST responses

See Also