live.voxport.podcast.episode

voxport.live

Schema Diff

+2 -2

From

CID
bafyreifyezrz3mc...
Indexed At
2026-09-01 18:32 UTC
View this version

To

CID
bafyreibtvk5eds3...
Indexed At
2026-09-09 15:42 UTC
View this version

Compatibility Analysis

Breaking Changes Detected

3 breaking changes, 2 non-breaking changes.

Breaking Changes (3)
  • RemovedVertex RemovedVertex { vertex_id: "live.voxport.podcast.series" }
  • RemovedEdge RemovedEdge { src: "live.voxport.podcast.episode:body.series", tgt: "live.voxport.podcast.series", kind: "ref", name: None }
  • ConstraintTightened ConstraintTightened { vertex_id: "live.voxport.podcast.episode:body.series", sort: "ref", old_value: "live.voxport.podcast.series", new_value: "live.voxport.podcast.series#seriesRef" }
Non-Breaking Changes (2)
  • AddedVertex AddedVertex { vertex_id: "live.voxport.podcast.series#seriesRef" }
  • AddedEdge AddedEdge { src: "live.voxport.podcast.episode:body.series", tgt: "live.voxport.podcast.series#seriesRef", kind: "ref", name: None }

Migration Guidance

Removed Elements

  • RemovedVertex { vertex_id: "live.voxport.podcast.series" }

Added Elements

  • AddedVertex { vertex_id: "live.voxport.podcast.series#seriesRef" }

Constraint Changes

  • ConstraintTightened ConstraintTightened { vertex_id: "live.voxport.podcast.episode:body.series", sort: "ref", old_value: "live.voxport.podcast.series", new_value: "live.voxport.podcast.series#seriesRef" }

Additional Notes

  • Breaking: RemovedEdge { src: "live.voxport.podcast.episode:body.series", tgt: "live.voxport.podcast.series", kind: "ref", name: None }
  • Non-breaking: AddedEdge { src: "live.voxport.podcast.episode:body.series", tgt: "live.voxport.podcast.series#seriesRef", kind: "ref", name: None }
1 1
{
2 2
  "$type": "com.atproto.lexicon.schema",
3 3
  "defs": {
4 4
    "chapter": {
5 5
      "properties": {
6 6
        "endTime": {
7 7
          "description": "End time in seconds (optional)",
8 8
          "type": "integer"
9 9
        },
10 10
        "hidden": {
11 11
          "description": "Whether the chapter is hidden from the table of contents (Podcasting 2.0 toc:false)",
12 12
          "type": "boolean"
13 13
        },
14 14
        "image": {
15 15
          "format": "uri",
16 16
          "type": "string"
17 17
        },
18 18
        "startTime": {
19 19
          "description": "Start time in seconds",
20 20
          "type": "integer"
21 21
        },
22 22
        "title": {
23 23
          "maxLength": 256,
24 24
          "type": "string"
25 25
        },
26 26
        "url": {
27 27
          "description": "Chapter link URL (Podcasting 2.0 chapter url)",
28 28
          "format": "uri",
29 29
          "type": "string"
30 30
        }
31 31
      },
32 32
      "required": [
33 33
        "title",
34 34
        "startTime"
35 35
      ],
36 36
      "type": "object"
37 37
    },
38 38
    "main": {
39 39
      "description": "A podcast episode belonging to a series",
40 40
      "key": "tid",
41 41
      "record": {
42 42
        "properties": {
43 43
          "audio": {
44 44
            "accept": [
45 45
              "audio/mpeg",
46 46
              "audio/mp4",
47 47
              "audio/wav",
48 48
              "audio/ogg",
49 49
              "audio/aac"
50 50
            ],
51 51
            "description": "Audio file reference",
52 52
            "maxSize": 524288000,
53 53
            "type": "blob"
54 54
          },
55 55
          "audioLength": {
56 56
            "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.",
57 57
            "minimum": 0,
58 58
            "type": "integer"
59 59
          },
60 60
          "audioType": {
61 61
            "description": "MIME type of the audio file when using audioUrl. Pairs with audioLength for RSS enclosure reconstruction.",
62 62
            "knownValues": [
63 63
              "audio/mpeg",
64 64
              "audio/mp4",
65 65
              "audio/wav",
66 66
              "audio/ogg",
67 67
              "audio/aac"
68 68
            ],
69 69
            "maxLength": 64,
70 70
            "type": "string"
71 71
          },
72 72
          "audioUrl": {
73 73
            "description": "External audio URL (if not using blob)",
74 74
            "format": "uri",
75 75
            "type": "string"
76 76
          },
77 77
          "blueskyPostRef": {
78 78
            "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.",
79 79
            "ref": "com.atproto.repo.strongRef",
80 80
            "type": "ref"
81 81
          },
82 82
          "chapters": {
83 83
            "description": "Episode chapters/timestamps",
84 84
            "items": {
85 85
              "ref": "#chapter",
86 86
              "type": "ref"
87 87
            },
88 88
            "maxLength": 100,
89 89
            "type": "array"
90 90
          },
91 91
          "createdAt": {
92 92
            "format": "datetime",
93 93
            "type": "string"
94 94
          },
95 95
          "duration": {
96 96
            "description": "Duration in seconds",
97 97
            "minimum": 0,
98 98
            "type": "integer"
99 99
          },
100 100
          "episodeNumber": {
101 101
            "description": "Episode number in series",
102 102
            "minimum": 1,
103 103
            "type": "integer"
104 104
          },
105 105
          "episodeType": {
106 106
            "default": "full",
107 107
            "description": "Episode type per RSS spec",
108 108
            "knownValues": [
109 109
              "full",
110 110
              "trailer",
111 111
              "bonus"
112 112
            ],
113 113
            "maxLength": 16,
114 114
            "type": "string"
115 115
          },
116 116
          "explicit": {
117 117
            "description": "Whether episode is explicit",
118 118
            "type": "boolean"
119 119
          },
120 120
          "image": {
121 121
            "accept": [
122 122
              "image/png",
123 123
              "image/jpeg"
124 124
            ],
125 125
            "description": "Episode-specific cover art",
126 126
            "maxSize": 2097152,
127 127
            "type": "blob"
128 128
          },
129 129
          "importedGuid": {
130 130
            "description": "Original GUID from RSS feed (for imported episodes)",
131 131
            "maxLength": 2048,
132 132
            "type": "string"
133 133
          },
134 134
          "persons": {
135 135
            "description": "People associated with this episode — hosts, guests, co-hosts (Podcasting 2.0 podcast:person)",
136 136
            "items": {
137 137
              "ref": "#person",
138 138
              "type": "ref"
139 139
            },
140 140
            "maxLength": 50,
141 141
            "type": "array"
142 142
          },
143 143
          "publishedAt": {
144 144
            "description": "Publication date (null = draft)",
145 145
            "format": "datetime",
146 146
            "type": "string"
147 147
          },
148 148
          "seasonNumber": {
149 149
            "description": "Season number",
150 150
            "minimum": 1,
151 151
            "type": "integer"
152 152
          },
153 153
          "series": {
154 -
            "description": "Reference to parent series",
155 -
            "ref": "live.voxport.podcast.series",
154 +
            "description": "Reference to parent series (uri + optional cid)",
155 +
            "ref": "live.voxport.podcast.series#seriesRef",
156 156
            "type": "ref"
157 157
          },
158 158
          "soundbites": {
159 159
            "description": "Short clip markers for apps to surface (Podcasting 2.0 podcast:soundbite)",
160 160
            "items": {
161 161
              "ref": "#soundbite",
162 162
              "type": "ref"
163 163
            },
164 164
            "maxLength": 20,
165 165
            "type": "array"
166 166
          },
167 167
          "summary": {
168 168
            "description": "Episode description/show notes (supports markdown)",
169 169
            "maxLength": 10000,
170 170
            "type": "string"
171 171
          },
172 172
          "tags": {
173 173
            "description": "Episode tags/keywords",
174 174
            "items": {
175 175
              "maxLength": 64,
176 176
              "type": "string"
177 177
            },
178 178
            "maxLength": 20,
179 179
            "type": "array"
180 180
          },
181 181
          "title": {
182 182
            "description": "Episode title",
183 183
            "maxLength": 256,
184 184
            "type": "string"
185 185
          },
186 186
          "transcript": {
187 187
            "description": "Episode transcript (singular — deprecated, prefer transcripts array)",
188 188
            "ref": "#transcript",
189 189
            "type": "ref"
190 190
          },
191 191
          "transcripts": {
192 192
            "description": "Episode transcripts — multiple entries for different languages/formats (Podcasting 2.0 podcast:transcript). Preferred over the singular transcript field.",
193 193
            "items": {
194 194
              "ref": "#transcript",
195 195
              "type": "ref"
196 196
            },
197 197
            "maxLength": 20,
198 198
            "type": "array"
199 199
          },
200 200
          "updatedAt": {
201 201
            "format": "datetime",
202 202
            "type": "string"
203 203
          }
204 204
        },
205 205
        "required": [
206 206
          "series",
207 207
          "title",
208 208
          "createdAt"
209 209
        ],
210 210
        "type": "object"
211 211
      },
212 212
      "type": "record"
213 213
    },
214 214
    "person": {
215 215
      "properties": {
216 216
        "did": {
217 217
          "description": "ATProto DID if the person has a verified identity",
218 218
          "format": "did",
219 219
          "type": "string"
220 220
        },
221 221
        "group": {
222 222
          "description": "Group from the Podcast Taxonomy Project (e.g. cast, writing, visuals). Defaults to 'cast' if absent.",
223 223
          "maxLength": 64,
224 224
          "type": "string"
225 225
        },
226 226
        "handle": {
227 227
          "description": "ATProto handle (e.g. alice.bsky.social) if the person has a Bluesky account",
228 228
          "format": "handle",
229 229
          "type": "string"
230 230
        },
231 231
        "image": {
232 232
          "description": "Profile image URL (maps to podcast:person img attribute)",
233 233
          "format": "uri",
234 234
          "type": "string"
235 235
        },
236 236
        "name": {
237 237
          "description": "Full name or alias of the person (spec: max 128 chars)",
238 238
          "maxLength": 128,
239 239
          "type": "string"
240 240
        },
241 241
        "role": {
242 242
          "description": "Role from the Podcast Taxonomy Project (e.g. host, guest, co-host). Defaults to 'host' if absent.",
243 243
          "maxLength": 64,
244 244
          "type": "string"
245 245
        },
246 246
        "url": {
247 247
          "description": "External profile URL (maps to podcast:person href attribute)",
248 248
          "format": "uri",
249 249
          "type": "string"
250 250
        }
251 251
      },
252 252
      "required": [
253 253
        "name"
254 254
      ],
255 255
      "type": "object"
256 256
    },
257 257
    "soundbite": {
258 258
      "properties": {
259 259
        "duration": {
260 260
          "description": "Duration in fractional seconds (e.g. '60.0', '42.25'). Matches Podcasting 2.0 spec exactly.",
261 261
          "maxLength": 16,
262 262
          "type": "string"
263 263
        },
264 264
        "startTime": {
265 265
          "description": "Start time in fractional seconds (e.g. '73.0', '1234.5'). Matches Podcasting 2.0 spec exactly.",
266 266
          "maxLength": 16,
267 267
          "type": "string"
268 268
        },
269 269
        "title": {
270 270
          "description": "Title/label for the soundbite (spec: max 128 chars)",
271 271
          "maxLength": 128,
272 272
          "type": "string"
273 273
        }
274 274
      },
275 275
      "required": [
276 276
        "startTime",
277 277
        "duration"
278 278
      ],
279 279
      "type": "object"
280 280
    },
281 281
    "transcript": {
282 282
      "properties": {
283 283
        "language": {
284 284
          "description": "Language of the transcript (defaults to feed language if absent)",
285 285
          "format": "language",
286 286
          "type": "string"
287 287
        },
288 288
        "rel": {
289 289
          "description": "If 'captions', the file is a closed captions file with time codes",
290 290
          "knownValues": [
291 291
            "captions"
292 292
          ],
293 293
          "maxLength": 16,
294 294
          "type": "string"
295 295
        },
296 296
        "type": {
297 297
          "description": "MIME type of the transcript file",
298 298
          "knownValues": [
299 299
            "text/plain",
300 300
            "text/html",
301 301
            "text/vtt",
302 302
            "application/x-subrip",
303 303
            "application/json"
304 304
          ],
305 305
          "maxLength": 64,
306 306
          "type": "string"
307 307
        },
308 308
        "url": {
309 309
          "description": "URL of the transcript file",
310 310
          "format": "uri",
311 311
          "type": "string"
312 312
        }
313 313
      },
314 314
      "required": [
315 315
        "url",
316 316
        "type"
317 317
      ],
318 318
      "type": "object"
319 319
    }
320 320
  },
321 321
  "description": "A podcast episode belonging to a series",
322 322
  "id": "live.voxport.podcast.episode",
323 323
  "lexicon": 1
324 324
}

Compare Other Versions

Lexicon Garden

@