# live.voxport.podcast.episode

> Published by [voxport.live](https://lexicon.garden/identity/did:plc:hhtgwctzcuhlkqrxu526sco5)

✓ This is the authoritative definition for this NSID.

## Description

A podcast episode belonging to a series

## Links

- [View on Lexicon Garden](https://lexicon.garden/lexicon/did:plc:hhtgwctzcuhlkqrxu526sco5/live.voxport.podcast.episode)
- [Documentation](https://lexicon.garden/lexicon/did:plc:hhtgwctzcuhlkqrxu526sco5/live.voxport.podcast.episode/docs)
- [Examples](https://lexicon.garden/lexicon/did:plc:hhtgwctzcuhlkqrxu526sco5/live.voxport.podcast.episode/examples)

## Definitions

### `live.voxport.podcast.episode`

**Type**: `record`

A podcast episode belonging to a series

**Key**: `tid`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `tags` | `array` | No | Episode tags/keywords |
| `audio` | `blob` | No | Audio file reference |
| `image` | `blob` | No | Episode-specific cover art |
| `title` | `string` | Yes | Episode title |
| `series` | `ref` → `live.voxport.podcast.series#seriesRef` | Yes | Reference to parent series (uri + optional cid) |
| `persons` | `array` | No | People associated with this episode — hosts, guests, co-hosts (Podcasting 2.0 podcast:person) |
| `summary` | `string` | No | Episode description/show notes (supports markdown) |
| `audioUrl` | `string` (uri) | No | External audio URL (if not using blob) |
| `chapters` | `array` | No | Episode chapters/timestamps |
| `duration` | `integer` | No | Duration in seconds |
| `explicit` | `boolean` | No | Whether episode is explicit |
| `audioType` | `string` | No | MIME type of the audio file when using audioUrl. Pairs with audioLength for RSS enclosure reconstruction. |
| `createdAt` | `string` (datetime) | Yes |  |
| `updatedAt` | `string` (datetime) | No |  |
| `soundbites` | `array` | No | Short clip markers for apps to surface (Podcasting 2.0 podcast:soundbite) |
| `transcript` | `ref` → `#transcript` | No | Episode transcript (singular — deprecated, prefer transcripts array) |
| `audioLength` | `integer` | No | Byte size of the audio file when using audioUrl. Required for a valid RSS enclosure — without this, a firehose consumer cannot reconstruct the feed from records alone. |
| `episodeType` | `string` | No | Episode type per RSS spec |
| `publishedAt` | `string` (datetime) | No | Publication date (null = draft) |
| `transcripts` | `array` | No | Episode transcripts — multiple entries for different languages/formats (Podcasting 2.0 podcast:transcript). Preferred over the singular transcript field. |
| `importedGuid` | `string` | No | Original GUID from RSS feed (for imported episodes) |
| `seasonNumber` | `integer` | No | Season number |
| `episodeNumber` | `integer` | No | Episode number in series |
| `blueskyPostRef` | `ref` → `com.atproto.repo.strongRef` | No | Reference to the Atmosphere cross-post (app.bsky.feed.post) for this episode. Set when the episode is auto-cross-posted, enabling custom feed operators to discover the cross-post from the episode record. |

### `live.voxport.podcast.episode#person`

**Type**: `object`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `did` | `string` (did) | No | ATProto DID if the person has a verified identity |
| `url` | `string` (uri) | No | External profile URL (maps to podcast:person href attribute) |
| `name` | `string` | Yes | Full name or alias of the person (spec: max 128 chars) |
| `role` | `string` | No | Role from the Podcast Taxonomy Project (e.g. host, guest, co-host). Defaults to 'host' if absent. |
| `group` | `string` | No | Group from the Podcast Taxonomy Project (e.g. cast, writing, visuals). Defaults to 'cast' if absent. |
| `image` | `string` (uri) | No | Profile image URL (maps to podcast:person img attribute) |
| `handle` | `string` (handle) | No | ATProto handle (e.g. alice.bsky.social) if the person has a Bluesky account |

### `live.voxport.podcast.episode#chapter`

**Type**: `object`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `url` | `string` (uri) | No | Chapter link URL (Podcasting 2.0 chapter url) |
| `image` | `string` (uri) | No |  |
| `title` | `string` | Yes |  |
| `hidden` | `boolean` | No | Whether the chapter is hidden from the table of contents (Podcasting 2.0 toc:false) |
| `endTime` | `integer` | No | End time in seconds (optional) |
| `startTime` | `integer` | Yes | Start time in seconds |

### `live.voxport.podcast.episode#soundbite`

**Type**: `object`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `title` | `string` | No | Title/label for the soundbite (spec: max 128 chars) |
| `duration` | `string` | Yes | Duration in fractional seconds (e.g. '60.0', '42.25'). Matches Podcasting 2.0 spec exactly. |
| `startTime` | `string` | Yes | Start time in fractional seconds (e.g. '73.0', '1234.5'). Matches Podcasting 2.0 spec exactly. |

### `live.voxport.podcast.episode#transcript`

**Type**: `object`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `rel` | `string` | No | If 'captions', the file is a closed captions file with time codes |
| `url` | `string` (uri) | Yes | URL of the transcript file |
| `type` | `string` | Yes | MIME type of the transcript file |
| `language` | `string` (language) | No | Language of the transcript (defaults to feed language if absent) |

## Raw Schema

```json
{
  "id": "live.voxport.podcast.episode",
  "defs": {
    "main": {
      "key": "tid",
      "type": "record",
      "record": {
        "type": "object",
        "required": [
          "series",
          "title",
          "createdAt"
        ],
        "properties": {
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 64
            },
            "maxLength": 20,
            "description": "Episode tags/keywords"
          },
          "audio": {
            "type": "blob",
            "accept": [
              "audio/mpeg",
              "audio/mp4",
              "audio/wav",
              "audio/ogg",
              "audio/aac"
            ],
            "maxSize": 524288000,
            "description": "Audio file reference"
          },
          "image": {
            "type": "blob",
            "accept": [
              "image/png",
              "image/jpeg"
            ],
            "maxSize": 2097152,
            "description": "Episode-specific cover art"
          },
          "title": {
            "type": "string",
            "maxLength": 256,
            "description": "Episode title"
          },
          "series": {
            "ref": "live.voxport.podcast.series#seriesRef",
            "type": "ref",
            "description": "Reference to parent series (uri + optional cid)"
          },
          "persons": {
            "type": "array",
            "items": {
              "ref": "#person",
              "type": "ref"
            },
            "maxLength": 50,
            "description": "People associated with this episode — hosts, guests, co-hosts (Podcasting 2.0 podcast:person)"
          },
          "summary": {
            "type": "string",
            "maxLength": 10000,
            "description": "Episode description/show notes (supports markdown)"
          },
          "audioUrl": {
            "type": "string",
            "format": "uri",
            "description": "External audio URL (if not using blob)"
          },
          "chapters": {
            "type": "array",
            "items": {
              "ref": "#chapter",
              "type": "ref"
            },
            "maxLength": 100,
            "description": "Episode chapters/timestamps"
          },
          "duration": {
            "type": "integer",
            "minimum": 0,
            "description": "Duration in seconds"
          },
          "explicit": {
            "type": "boolean",
            "description": "Whether episode is explicit"
          },
          "audioType": {
            "type": "string",
            "maxLength": 64,
            "description": "MIME type of the audio file when using audioUrl. Pairs with audioLength for RSS enclosure reconstruction.",
            "knownValues": [
              "audio/mpeg",
              "audio/mp4",
              "audio/wav",
              "audio/ogg",
              "audio/aac"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          },
          "updatedAt": {
            "type": "string",
            "format": "datetime"
          },
          "soundbites": {
            "type": "array",
            "items": {
              "ref": "#soundbite",
              "type": "ref"
            },
            "maxLength": 20,
            "description": "Short clip markers for apps to surface (Podcasting 2.0 podcast:soundbite)"
          },
          "transcript": {
            "ref": "#transcript",
            "type": "ref",
            "description": "Episode transcript (singular — deprecated, prefer transcripts array)"
          },
          "audioLength": {
            "type": "integer",
            "minimum": 0,
            "description": "Byte size of the audio file when using audioUrl. Required for a valid RSS enclosure — without this, a firehose consumer cannot reconstruct the feed from records alone."
          },
          "episodeType": {
            "type": "string",
            "default": "full",
            "maxLength": 16,
            "description": "Episode type per RSS spec",
            "knownValues": [
              "full",
              "trailer",
              "bonus"
            ]
          },
          "publishedAt": {
            "type": "string",
            "format": "datetime",
            "description": "Publication date (null = draft)"
          },
          "transcripts": {
            "type": "array",
            "items": {
              "ref": "#transcript",
              "type": "ref"
            },
            "maxLength": 20,
            "description": "Episode transcripts — multiple entries for different languages/formats (Podcasting 2.0 podcast:transcript). Preferred over the singular transcript field."
          },
          "importedGuid": {
            "type": "string",
            "maxLength": 2048,
            "description": "Original GUID from RSS feed (for imported episodes)"
          },
          "seasonNumber": {
            "type": "integer",
            "minimum": 1,
            "description": "Season number"
          },
          "episodeNumber": {
            "type": "integer",
            "minimum": 1,
            "description": "Episode number in series"
          },
          "blueskyPostRef": {
            "ref": "com.atproto.repo.strongRef",
            "type": "ref",
            "description": "Reference to the Atmosphere cross-post (app.bsky.feed.post) for this episode. Set when the episode is auto-cross-posted, enabling custom feed operators to discover the cross-post from the episode record."
          }
        }
      },
      "description": "A podcast episode belonging to a series"
    },
    "person": {
      "type": "object",
      "required": [
        "name"
      ],
      "properties": {
        "did": {
          "type": "string",
          "format": "did",
          "description": "ATProto DID if the person has a verified identity"
        },
        "url": {
          "type": "string",
          "format": "uri",
          "description": "External profile URL (maps to podcast:person href attribute)"
        },
        "name": {
          "type": "string",
          "maxLength": 128,
          "description": "Full name or alias of the person (spec: max 128 chars)"
        },
        "role": {
          "type": "string",
          "maxLength": 64,
          "description": "Role from the Podcast Taxonomy Project (e.g. host, guest, co-host). Defaults to 'host' if absent."
        },
        "group": {
          "type": "string",
          "maxLength": 64,
          "description": "Group from the Podcast Taxonomy Project (e.g. cast, writing, visuals). Defaults to 'cast' if absent."
        },
        "image": {
          "type": "string",
          "format": "uri",
          "description": "Profile image URL (maps to podcast:person img attribute)"
        },
        "handle": {
          "type": "string",
          "format": "handle",
          "description": "ATProto handle (e.g. alice.bsky.social) if the person has a Bluesky account"
        }
      }
    },
    "chapter": {
      "type": "object",
      "required": [
        "title",
        "startTime"
      ],
      "properties": {
        "url": {
          "type": "string",
          "format": "uri",
          "description": "Chapter link URL (Podcasting 2.0 chapter url)"
        },
        "image": {
          "type": "string",
          "format": "uri"
        },
        "title": {
          "type": "string",
          "maxLength": 256
        },
        "hidden": {
          "type": "boolean",
          "description": "Whether the chapter is hidden from the table of contents (Podcasting 2.0 toc:false)"
        },
        "endTime": {
          "type": "integer",
          "description": "End time in seconds (optional)"
        },
        "startTime": {
          "type": "integer",
          "description": "Start time in seconds"
        }
      }
    },
    "soundbite": {
      "type": "object",
      "required": [
        "startTime",
        "duration"
      ],
      "properties": {
        "title": {
          "type": "string",
          "maxLength": 128,
          "description": "Title/label for the soundbite (spec: max 128 chars)"
        },
        "duration": {
          "type": "string",
          "maxLength": 16,
          "description": "Duration in fractional seconds (e.g. '60.0', '42.25'). Matches Podcasting 2.0 spec exactly."
        },
        "startTime": {
          "type": "string",
          "maxLength": 16,
          "description": "Start time in fractional seconds (e.g. '73.0', '1234.5'). Matches Podcasting 2.0 spec exactly."
        }
      }
    },
    "transcript": {
      "type": "object",
      "required": [
        "url",
        "type"
      ],
      "properties": {
        "rel": {
          "type": "string",
          "maxLength": 16,
          "description": "If 'captions', the file is a closed captions file with time codes",
          "knownValues": [
            "captions"
          ]
        },
        "url": {
          "type": "string",
          "format": "uri",
          "description": "URL of the transcript file"
        },
        "type": {
          "type": "string",
          "maxLength": 64,
          "description": "MIME type of the transcript file",
          "knownValues": [
            "text/plain",
            "text/html",
            "text/vtt",
            "application/x-subrip",
            "application/json"
          ]
        },
        "language": {
          "type": "string",
          "format": "language",
          "description": "Language of the transcript (defaults to feed language if absent)"
        }
      }
    }
  },
  "$type": "com.atproto.lexicon.schema",
  "lexicon": 1,
  "description": "A podcast episode belonging to a series"
}
```
