Skip to content

Commit 3a68b75

Browse files
committed
Update Episodes enpoint spec
Explain `download_status` and timestamp field & update table formatting on overview page. Add placeholder page for matching & deduplication. Specify GET /episodes.
1 parent d2991b6 commit 3a68b75

3 files changed

Lines changed: 350 additions & 26 deletions

File tree

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
---
2+
title: Get all episodes
3+
description: Get all episodes for a user
4+
sidebar:
5+
order: 3
6+
badge:
7+
text: Core
8+
variant: caution
9+
---
10+
11+
import CoreAction from "@partials/_core-action.mdx";
12+
13+
<CoreAction />
14+
15+
```http title="Endpoint"
16+
GET /v1/episodes
17+
```
18+
19+
This endpoint enables clients to return all episode information relating to the authenticated user. It returns pagination information and an array of `episodes`.
20+
21+
While supported, this endpoint is expected to be used rarely in practice. More often, episodes are retrieved per queue (TBD) or per subscription (TBD).
22+
23+
## Response fields
24+
25+
### Metadata
26+
27+
| Field | Type | Required? | Description |
28+
| ---------- | ------ | --------- | ------------------------------------------------ |
29+
| `total` | Number | Yes | The total number of objects returned by the call |
30+
| `page` | Number | Yes | The number of the page returned in the call |
31+
| `per_page` | Number | Yes | The number of results returned per page |
32+
| `next` | String | No | The URL for the next page of results |
33+
| `previous` | String | No | The URL for the previous page of results |
34+
35+
### Episode fields
36+
37+
| Group | Field | Type | Required? | Description |
38+
| ---------- | ----------------------------------- | --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39+
| Identifier | `podcast_guid` | String \<UUID\> | Yes | The globally unique ID of the parent podcast |
40+
| Identifier | `sync_id` | String \<UUID\> | Yes | The synchronisation ID of the episode, globally unique at the server and its clients |
41+
| Identifier | `episode_guid` | String \<UUID\> | Yes | The globally unique ID of the episode, as present in the RSS feed ([`guid` tag](https://www.rssboard.org/rss-specification#ltguidgtSubelementOfLtitemgt)) |
42+
| Identifier | `title` | String | Yes | The title of the episode, as present in the RSS feed (`title` tag) |
43+
| Identifier | `publish_date` | Datetime | Yes | The date of publishing of the episode, as present in the RSS feed ([`pubDate` tag](https://www.rssboard.org/rss-specification#ltpubdategtSubelementOfLtitemgt)). Presented in [ISO 8601 format] |
44+
| Identifier | `enclosure_url` | String | Yes | The media file of the episode, as present in the RSS feed ([`enclosure` tag](https://www.rssboard.org/rss-specification#ltenclosuregtSubelementOfLtitemgt)) |
45+
| Identifier | `episode_url` | String | Yes | The (webpage) URL of the episode, as present in the RSS feed (`link`tag) |
46+
| Data | `playback_position` | Integer | ??YES/NO??| The most recent playback position in seconds |
47+
| Data | `played_status` | Boolean | No | Whether the episode has been (marked as such) |
48+
| Data | `new_status` <BadgeOptional /> | Boolean | No | Whether the user (manually) interacted with the episode.<br />_Example:_ In AntennaPod this is used to indicate whether an episode is in the Inbox |
49+
| Data | `download_status` <BadgeOptional /> | Boolean | No | Whether the episode is downloaded on the client. For further details, see below. |
50+
| Data | `favorite_status` <BadgeOptional /> | Boolean | No | Whether the episode has been favorited by the user |
51+
52+
The server can ignore the other identifier fields, if it found an episode based on the `podcast_guid` and the `sync_id`.
53+
54+
:::note[Why all idenifiers are required]
55+
56+
Assume client A has refreshed a feed locally, and client B hasn't done so yet. In order for client B to do episode matching with what it receives from the server, it would need to have more data than just the `sync_id`. In this scenario, how does the server know whether client B has already refreshed the feed and is aware of the episode or not (i.e. whether it can rely on `sync_id` alone or needs to send more information)? The server cannot know, thus a 'conversation' between server and client would be needed:
57+
* `S` "here's a new episode with podcast_guid x and sync_id y"
58+
* `C` "I don't recognise this one - tell me more!"
59+
* `S` "Ok, here's all information you can use for episode matching: …"
60+
61+
To avoid such conversation, and as the sending of all matching data involves only a few bytes, all identifiers are sent always.
62+
63+
:::
64+
65+
Remember that each of the data fields MUST have both a 'value' and a 'timestamp'.
66+
67+
:::note[Discussion details]
68+
See meeting notes from [2024-06-19](https://pad.funkwhale.audio/s/I3F_C2NbQ#GET-episode-information)
69+
:::
70+
71+
## Parameters
72+
73+
The client MAY add the following parameters to their call:
74+
75+
| Field | Type | In | Required? | Description |
76+
| ---------- | -------- | ----- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77+
| `since` | DateTime | Query | No | The date from which the server should return objects. The server only returns entries whose `timestamp` data for any of the fields are greater than this parameter. Expected in [ISO 8601 format] |
78+
| `page` | Number | Query | No | The page of results to be returned by the server. Defaults to `1` if not present |
79+
| `per_page` | Number | Query | No | The number of results to return in each call. Defaults to `50` if not present |
80+
81+
:::note
82+
If no `since` parameter is provided, the server MUST return all current subscription information.
83+
:::
84+
85+
## Server-side behavior
86+
87+
No particular behavior expected.
88+
89+
## Client behavior
90+
91+
The client SHOULD update its local episode data to match the information returned in the response.
92+
93+
## Example request
94+
95+
<Tabs syncKey="accepts">
96+
<TabItem label="JSON">
97+
98+
```console
99+
$ curl -X 'GET' \
100+
'/v1/episodes?since=2024-04-23T18%3A25%3A34.511Z&page=1&per_page=5' \
101+
-H 'accept: application/json'
102+
```
103+
104+
</TabItem>
105+
<TabItem label="XML">
106+
107+
```console
108+
$ curl -X 'GET' \
109+
'/v1/episodes?since=2024-04-23T18%3A25%3A34.511Z&page=1&per_page=5' \
110+
-H 'accept: application/xml'
111+
```
112+
113+
</TabItem>
114+
</Tabs>
115+
116+
## Example 200 response
117+
118+
<Tabs syncKey="accepts">
119+
<TabItem label="JSON">
120+
121+
```json
122+
{
123+
"total": 2,
124+
"page": 1,
125+
"per_page": 5,
126+
"episodes": [
127+
{
128+
"podcast_guid": "31740ac6-e39d-49cd-9179-634bcecf4143",
129+
"sync_id": "cff3ea32-4215-4f98-bc23-5358d1f35b55",
130+
"episode_guid": "https://example.com/podcast/episode-5-the-history-of-RSS",
131+
"title": "The history of RSS",
132+
"publish_date": "2022-04-24T17:53:21.573Z",
133+
"enclosure_url": "https://example.com/podcast/episode-5-the-history-of-RSS.mp3",
134+
"episode_url": "https://example.com/podcast/episode-5-the-history-of-RSS",
135+
"playback_position": {
136+
"value": 0,
137+
"timestamp": "2024-11-02T13:19"
138+
},
139+
"played_status": {
140+
"value": true,
141+
"timestamp": "2024-11-02T13:19"
142+
},
143+
"new_status": {
144+
"value": false,
145+
"timestamp": "2024-10-30T17:31"
146+
},
147+
"download_status": {
148+
"value": false,
149+
"timestamp": "2024-11-02T13:19"
150+
},
151+
"favorite_status": {
152+
"value": false,
153+
"timestamp": "2024-11-02T13:19"
154+
}
155+
},
156+
{
157+
"podcast_guid": "9d6786c9-ed48-470d-acbe-e593547f4b5b",
158+
"sync_id": "5773f457-e71b-417d-8ea8-f07c38a03a3e",
159+
"episode_guid": "01999e25-08cd-4f29-a61e-6ca459b40d27",
160+
"title": "Walk with the weatherman",
161+
"publish_date": "2022-04-27T19:35:20.000Z",
162+
"enclosure_url": "https://op3.dev/e/https://podcasts.example2.net/audio/@digitalcitizen/49-walk-with-the-weatherman.mp3",
163+
"episode_url": "https://podcasts.example2.net/@digitalcitizen/episodes/49-walk-with-the-weatherman",
164+
"playback_position": {
165+
"value": 2100,
166+
"timestamp": "2024-11-01T17:38"
167+
},
168+
"played_status": {
169+
"value": false,
170+
"timestamp": "2024-04-28T09:20"
171+
},
172+
"new_status": {
173+
"value": false,
174+
"timestamp": "2024-11-01T17:02"
175+
},
176+
"download_status": {
177+
"value": true,
178+
"timestamp": "2024-11-01T17:02"
179+
},
180+
"favorite_status": {
181+
"value": false,
182+
"timestamp": "2024-04-28T09:20"
183+
}
184+
}
185+
]
186+
}
187+
```
188+
189+
</TabItem>
190+
<TabItem label="XML">
191+
192+
```xml
193+
<?xml version="1.0" encoding="UTF-8"?>
194+
<episodes>
195+
<total>2</total>
196+
<page>1</page>
197+
<per_page>5</per_page>
198+
<episode>
199+
<podcast_guid>31740ac6-e39d-49cd-9179-634bcecf4143</podcast_guid>
200+
<sync_id>cff3ea32-4215-4f98-bc23-5358d1f35b55</sync_id>
201+
<episode_guid>https://example.com/podcast/episode-5-the-history-of-RSS</episode_guid>
202+
<title>The history of RSS</title>
203+
<publish_date>2022-04-24T17:53:21.573Z</publish_date>
204+
<enclosure_url>https://example.com/podcast/episode-5-the-history-of-RSS.mp3</enclosure_url>
205+
<episode_url>https://example.com/podcast/episode-5-the-history-of-RSS</episode_url>
206+
<playback_position>
207+
<value>0</value>
208+
<timestamp>2024-11-02T13:19</timestamp>
209+
</playback_position>
210+
<played_status>
211+
<value>true</value>
212+
<timestamp>2024-11-02T13:19</timestamp>
213+
</played_status>
214+
<new_status>
215+
<value>false</value>
216+
<timestamp>2024-10-30T17:31</timestamp>
217+
</new_status>
218+
<download_status>
219+
<value>false</value>
220+
<timestamp>2024-11-02T13:19</timestamp>
221+
</download_status>
222+
<favorite_status>
223+
<value>false</value>
224+
<timestamp>2024-11-02T13:19</timestamp>
225+
</favorite_status>
226+
</episode>
227+
<episode>
228+
<podcast_guid>9d6786c9-ed48-470d-acbe-e593547f4b5b</podcast_guid>
229+
<sync_id>5773f457-e71b-417d-8ea8-f07c38a03a3e</sync_id>
230+
<episode_guid>01999e25-08cd-4f29-a61e-6ca459b40d27</episode_guid>
231+
<title>Walk with the weatherman</title>
232+
<publish_date>2022-04-27T19:35:20.000Z</publish_date>
233+
<enclosure_url>https://op3.dev/e/https://podcasts.example2.net/audio/@digitalcitizen/49-walk-with-the-weatherman.mp3</enclosure_url>
234+
<episode_url>https://podcasts.example2.net/@digitalcitizen/episodes/49-walk-with-the-weatherman</episode_url>
235+
<playback_position>
236+
<value>2100</value>
237+
<timestamp>2024-11-01T17:38</timestamp>
238+
</playback_position>
239+
<played_status>
240+
<value>false</value>
241+
<timestamp>2024-04-28T09:20</timestamp>
242+
</played_status>
243+
<new_status>
244+
<value>false</value>
245+
<timestamp>2024-11-01T17:02</timestamp>
246+
</new_status>
247+
<download_status>
248+
<value>true</value>
249+
<timestamp>2024-11-01T17:02</timestamp>
250+
</download_status>
251+
<favorite_status>
252+
<value>false</value>
253+
<timestamp>2024-04-28T09:20</timestamp>
254+
</favorite_status>
255+
</episode>
256+
</episodes>
257+
```
258+
259+
</TabItem>
260+
</Tabs>
261+
262+
[ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html

0 commit comments

Comments
 (0)