All episode paths begin with /podcasts/:podcast_id/episodes. Use the podcast and episode hashids returned by the API.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /podcasts/:podcast_id/episodes | List episodes with pagination and an optional status filter. |
GET | /podcasts/:podcast_id/episodes/:id | Read one episode and its audio and channel status. |
POST | /podcasts/:podcast_id/episodes | Create a draft. |
PATCH | /podcasts/:podcast_id/episodes/:id | Update metadata or replace audio/artwork. |
DELETE | /podcasts/:podcast_id/episodes/:id | Delete an episode; returns 204 No Content. |
Publishing and unpublishing use separate endpoints described in Publishing.
List and filter
GET /podcasts/:podcast_id/episodes?page=1&per_page=25&status=draft
status can be draft, scheduled, or published. Omit it for all episodes. page starts at 1. per_page defaults to 25 and cannot exceed 100. The response includes episodes and meta:
{
"episodes": [
/* episode objects */
],
"meta": {
"page": 1,
"per_page": 25,
"total_pages": 2,
"total_count": 29
}
}
Create a draft
POST /podcasts/:podcast_id/episodes
Content-Type: application/json
Idempotency-Key: editor-job-123
{
"episode": {
"title": "Field notes",
"summary": "A new update",
"description": "<p>Episode notes</p>",
"season_number": 2,
"episode_number": 4,
"episode_type": "full",
"explicit": false,
"tags": ["field-notes", "interview"],
"audio_upload_id": "SIGNED_UPLOAD_ID"
}
}
The response is 201 Created and the new episode is always a draft. audio_upload_id, audio_url, cover_url, and a multipart cover file are optional media fields; see Audio and artwork. You can also create the draft before attaching audio.
Send an Idempotency-Key header when a job may retry. Keys are scoped to one podcast, may be up to 255 bytes, and a repeated create with the same key returns the first episode instead of making another draft.
Accepted metadata fields are title, summary, description (HTML), description_markdown, season_number, episode_number, episode_type, explicit, tags, and chapters. Tags can be sent as an array or comma-separated text. The API does not accept podcast_id, account_id, or published_at in the episode body.
| Field | Rule |
|---|---|
season_number, episode_number | A positive whole number, or null. Anything else returns 422 invalid naming the field. |
episode_type | full, trailer, or bonus. Anything else returns 422 invalid. |
description_markdown | Show notes as Markdown, converted on the server; wins over description. See Show notes. |
chapters | The episode’s chapters. See Chapters. |
Update and delete
Use PATCH /podcasts/:podcast_id/episodes/:id with an episode object containing only the fields you want to change. To change publication time, use the publish endpoint; published_at in a PATCH body is ignored. Send "remove_cover": true to drop the episode’s own artwork and fall back to the show’s.
DELETE /podcasts/:podcast_id/episodes/:id permanently removes the episode and returns 204 No Content.
Episode response
| Field | Meaning |
|---|---|
id, podcast_id | Hashids. |
title, summary, season_number, episode_number, episode_type, explicit, tags | Metadata. |
description | Show notes as HTML. |
description_markdown, description_editable | Show notes as Markdown; see Show notes. |
status, published_at | Publication state; see below. |
audio | {status, url, duration, mime_type, size, error}; see Audio status. |
cover_url | The episode’s own artwork, or null when it uses the show’s. |
share_url | The public episode page, or null until the episode is live on a public channel. |
chapters, chapters_source, chapter_warnings | Chapters; see Chapters. |
channels | Releases: {id, name, channel_type, released_at, live} for each channel. |
created_at, updated_at | Timestamps. |
Responses also include fields for features these pages don’t cover yet, such as people, transcript, questions and campaign. You can ignore them.
status is based on the episode’s published_at: draft when absent, scheduled when in the future, and published when that time has arrived. A channel may still have a later released_at, so check channels for its actual release time.
Show notes
Show notes are rich text. description returns them as HTML; description_markdown returns them as Markdown, or null when they contain something Markdown can’t express, such as images. description_editable is true when the Markdown holds everything.
The Markdown covers paragraphs, line breaks, bold, italic, strikethrough, http, https and mailto links, lists, headings, quotes, and code blocks. Send description_markdown to update notes with formatting; if you send both, the Markdown is used. When description_editable is false, leave the notes alone or replace them deliberately.
Put {{chapters}} on its own line in the notes to show the episode’s chapter list in podcast apps and on its web page. It updates when the chapters change.
Chapters
Send chapters as Podcasting 2.0 objects. Times are in seconds, and the first chapter must start at 0:
{
"episode": {
"chapters": [
{ "startTime": 0, "title": "Cold open" },
{ "startTime": 94.5, "title": "The bakery at 3am", "url": "https://example.com", "img": "https://..." },
{ "startTime": 1210, "title": "Calls", "toc": false }
]
}
}
Each chapter has startTime and a title of 1–80 characters, and optionally endTime, url (http or https), img, and toc (true or false). Start times must be unique and before the audio ends. A problem returns 422 invalid with a param such as chapters[2].startTime. Send [] to remove the chapters.
Responses return the stored chapters, chapters_source (manual or ai), and chapter_warnings: advice such as chapters too close together for Spotify, which don’t stop you saving. Published episodes with chapters include a podcast:chapters link in their feed.