Skip to content

Commit 9340105

Browse files
committed
Core sync overhaul and new subscriptions approach
1 parent 3965df6 commit 9340105

6 files changed

Lines changed: 802 additions & 585 deletions

File tree

‎astro.config.ts‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import { defineConfig } from "astro/config";
2+
import { satteri } from '@astrojs/markdown-satteri';
23
import starlight from "@astrojs/starlight";
34
import starlightOpenAPI, { openAPISidebarGroups } from 'starlight-openapi'
45

56
export default defineConfig({
7+
markdown: {
8+
processor: satteri({
9+
features: {
10+
gfm: true,
11+
headingAttributes: true,
12+
directive: true,
13+
},
14+
})
15+
},
616
integrations: [
717
starlight({
818
title: "Open Podcast API",
@@ -41,14 +51,29 @@ export default defineConfig({
4151
label: "Introduction",
4252
link: "specs",
4353
},
54+
{
55+
label: "Conventions",
56+
link: "specs/conventions"
57+
},
58+
{
59+
label: "Authentication",
60+
link: "specs/authentication"
61+
},
62+
{
63+
label: "Synchronization",
64+
link: "specs/sync",
65+
badge: {
66+
text: "Core",
67+
variant: "success"
68+
}
69+
},
4470
{
4571
label: "Subscriptions",
46-
collapsed: true,
47-
items: [{
48-
autogenerate: {
49-
directory: "specs/subscriptions",
50-
}
51-
}],
72+
link: "specs/subscriptions",
73+
badge: {
74+
text: 'Core',
75+
variant: 'success'
76+
}
5277
},
5378
],
5479
},
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
---
2+
title: Authentication
3+
description: The Open Podcast API uses Bearer authentication for all protected endpoints.
4+
---
5+
6+
The Open Podcast API uses a bearer token authentication model based on a simplified OAuth 2.0-style login flow.
7+
8+
Clients authenticate with a username and password to obtain an access token and a refresh token. The access token is used to authorize API requests. When the access token expires, the client should use the refresh token to obtain a new access token. If the refresh token has expired, the user must authenticate again.
9+
10+
## Authentication flow {#authentication-flow}
11+
12+
1. The client submits the user's credentials to the `/auth/login` endpoint.
13+
1. The server validates the credentials and returns:
14+
* An access token
15+
* The access token expiration offset (in seconds)
16+
* A refresh token
17+
* The refresh token expiration time
18+
* The endpoint used to refresh the access token
19+
1. The client includes the access token in the `Authorization` header for all authenticated requests.
20+
1. When the access token expires, the client requests a new access token using the refresh token.
21+
1. If the refresh token has expired or is no longer valid, the client must authenticate again using the `/auth/login` endpoint.
22+
23+
## Login {#login}
24+
25+
```txt title="Endpoint"
26+
POST /auth/login
27+
```
28+
29+
### Request {#login-request}
30+
31+
```sh
32+
curl '/auth/login' \
33+
-X 'POST' \
34+
-H 'Content-Type: application/json' \
35+
-H 'Client-ID: cca4ad10-dc35-4572-8f11-125dc465428a'
36+
-d '{
37+
"username": "user@example.com",
38+
"password": "your-password"
39+
}'
40+
```
41+
42+
#### Successful response {#login-success-response}
43+
44+
```json title="HTTP 200 OK"
45+
{
46+
"access_token": "<access_token>",
47+
"expires_in": 3600,
48+
"refresh_token": "<refresh_token>",
49+
"refresh_expires": "2026-07-11T15:30:00Z",
50+
"refresh_endpoint": "/auth/refresh"
51+
}
52+
```
53+
54+
#### Response fields {#login-success-response-fields}
55+
56+
| Field | Description |
57+
|--------------------|----------------------------------------------|
58+
| `access_token` | Token used to authenticate API requests |
59+
| `expires_in` | Lifetime of the access token in seconds |
60+
| `refresh_token` | Token used to obtain a new access token |
61+
| `refresh_expires` | UTC timestamp when the refresh token expires |
62+
| `refresh_endpoint` | Endpoint used to refresh the access token |
63+
64+
## Refreshing an access token {#refreshing-access-token}
65+
66+
When the access token expires, the client should request a new one before making additional API calls.
67+
68+
```txt title="Endpoint"
69+
POST /auth/refresh
70+
```
71+
72+
### Request {#refresh-request}
73+
74+
```sh
75+
curl '/auth/refresh' \
76+
-X 'POST' \
77+
-H 'Content-Type: application/json' \
78+
-H 'Client-ID: cca4ad10-dc35-4572-8f11-125dc465428a'
79+
-d '{
80+
"refresh_token": "<refresh_token>"
81+
}'
82+
```
83+
84+
```json title="Successful response"
85+
{
86+
"access_token": "<new_access_token>",
87+
"expires_in": 3600
88+
}
89+
```
90+
91+
If the refresh token is expired or invalid, the server returns an authentication error (typically `401 Unauthorized`). The client must then authenticate again using the `/auth/login` endpoint.
92+
93+
## Using the Access Token {#use-access-token}
94+
95+
All authenticated requests must include the access token in the `Authorization` header using the Bearer authentication scheme.
96+
97+
### Example {#use-token-example}
98+
99+
```sh title="Example request"
100+
curl '/api/v1/sync \
101+
-H 'Content-Type: application/json' \
102+
-H 'Authorization: Bearer <access_token>' \
103+
-H 'Client-ID: cca4ad10-dc35-4572-8f11-125dc465428a'
104+
```
105+
106+
## Token Lifecycle {#token-lifecycle}
107+
108+
| State | Client Action |
109+
|-----------------------------------------|-----------------------------------------------------|
110+
| Access token is valid | Continue using the access token. |
111+
| Access token has expired | Request a new access token using the refresh token. |
112+
| Refresh token has expired or is invalid | Authenticate again using the `/login` endpoint. |
113+
114+
115+
## Error Responses {#error-responses}
116+
117+
| HTTP Status | Description |
118+
|-----------------------------|------------------------------------------------------------------------------|
119+
| `400 Bad Request` | Request is malformed or required fields are missing. |
120+
| `401 Unauthorized` | Credentials or tokens are invalid or expired. |
121+
| `403 Forbidden` | The authenticated user is not authorized to perform the requested operation. |
122+
| `500 Internal Server Error` | An unexpected server error occurred. |
123+
124+
## Security Considerations {#security-considerations}
125+
126+
* Always use HTTPS to protect credentials and tokens in transit.
127+
* Store access and refresh tokens securely.
128+
* Never expose tokens in URLs or log files.
129+
* Send the access token only in the Authorization header.
130+
* Treat refresh tokens as sensitive credentials and protect them accordingly.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
---
2+
title: Conventions
3+
description: All endpoints in the Open Podcast API follow the conventions outlined in this document.
4+
---
5+
6+
The following conventions are used for all endpoints in the Open Podcast API specification.
7+
8+
## Content type {#content-type}
9+
10+
All endpoints covered by the Open Podcast API require a `Content-Type` header to request JSON information from the server.
11+
12+
```txt
13+
Content-Type: application/json
14+
```
15+
16+
## Client IDs {#client-ids}
17+
18+
Each client must generate and persist a [UUIDv4](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-4) value that uniquely identifies that client installation or instance. Client IDs identify a client installation rather than a user or device.
19+
20+
Include this value with every API request using the `Client-ID` header.
21+
22+
```txt
23+
Client-ID: 550e8400-e29b-41d4-a716-446655440000
24+
```
25+
26+
:::note
27+
The client ID is used only to identify the source of synchronized changes. It is not an authentication credential and can be freely regenerated if the client state is reset.
28+
:::
29+
30+
### Requirements {#client-id-requirements}
31+
32+
* Generate the ID once, when the client is first initialized.
33+
* The ID must be a valid UUID version 4 (random UUID).
34+
* Persist the ID and reuse it for all future requests.
35+
* Do not generate a new ID for each request.
36+
* Each independent client installation or instance should have its own unique ID.
37+
38+
### Purpose {#client-id-purpose}
39+
40+
The server uses the client ID to identify the origin of changes. Endpoints that return updates can exclude changes created by the requesting client, allowing clients to synchronize only updates originating from other clients.
41+
42+
## Timestamps {#timestamps}
43+
44+
:::caution[Important]
45+
The synchronization protocol assumes that client clocks are reasonably accurate. Devices with significantly incorrect clocks may observe unexpected synchronization order.
46+
:::
47+
48+
All actions sent to the server must include a UTC timestamp recorded by the client. These timestamps determine action order. The server compares the client-supplied timestamp with the entity's current modification timestamp. An action with a later timestamp MUST be applied. An action with an earlier timestamp MUST be ignored.
49+
50+
Timestamps must be submitted in the [RFC3339 format](https://www.rfc-editor.org/rfc/rfc3339). Servers must accept any fractional precision. For example:
51+
52+
* `2026-07-11T15:30:00Z`
53+
* `2026-07-11T15:30:00.1Z`
54+
* `2026-07-11T15:30:00.123456Z`
55+
56+
Once generated, an action's timestamp must not be modified. Retried requests must preserve the original timestamp.
57+
58+
Actions contained within a batch are independent. Servers must order actions solely according to their timestamps rather than the order in which they appear in the request.
59+
60+
An action is applied only if its timestamp is strictly later than the entity's current `last_updated` timestamp. Actions with an earlier or identical timestamp are ignored.
61+
62+
## Synchronization model {#sync-model}
63+
64+
The Open Podcast API follows a batch synchronization model through a [synchronization endpoint](/specs/sync). Updates are processed in bulk using action names and entity types as discriminators. To reduce the risk of performance degradation while facilitating bulk updates from clients, updates are limited to 30 actions per request.
65+
66+
### Client behavior {#client-sync-behavior}
67+
68+
Synchronization of data is based on client timestamps. These are considered authoritative. When a user performs an action on a client, the client must record the UTC timestamp of the action and send it with the corresponding update action.
69+
70+
The client may send multiple bulk requests in sequence, starting from the earliest recorded timestamp. Requests are expected to be sent in chronological order.
71+
72+
### Server behavior {#server-sync-behavior}
73+
74+
The server modifies state based on a last-write-wins model where the action with the latest timestamp always applies.
75+
76+
The server must disregard any actions with a timestamp earlier than the current modification timestamp as stale. The server must always return the current canonical state of the requested entity whether the action was applied or not.
77+
78+
Clients may request information from a given offset timestamp to fetch all relevant updates that have occurred since their last sync. Servers must respond only with entities that have been updated since the offset timestamp. The [client ID](#client-ids) of the client that made the most recent change to an entity must be preserved and only updates not made by the requesting client should be returned.
79+
80+
In addition to the central sync endpoint, all entities must be made available for retrieval from domain-specific endpoints. Servers must provide bulk and single-item endpoints for each entity type.
81+
82+
## Pagination {#pagination}
83+
84+
All bulk `GET` requests must be paginated using a stable offset value. For the sync endpoint, this value must be a UTC timestamp in the RFC3339 format. For bulk entity endpoints, the entity ID or another stable incremental value should be used. The server must respond with navigation links in each response to point clients to the next and previous pages of results.
85+
86+
## Modifications {#modifications}
87+
88+
For each entity, servers must keep track of the `last_updated` timestamp and associated requesting client ID when a change is made to the entity metadata. When a client requests a sync response, any entity that has been modified by a different client must be returned.
89+
90+
## Response codes {#response-codes}
91+
92+
The Open Podcast API uses the following HTTP response codes:
93+
94+
* `200`: operation completed successfully.
95+
* `400`: the client sent an invalid request.
96+
* `401`: the client attempted to request a resource without authentication.
97+
* `403`: the client requested access to a resource it does not have permission to interact with.
98+
* `404`: the client attempted to access a non-existent resource.
99+
* `500`: the server encountered an error while performing a request.
100+
101+
## Core and optional functionality {#core-and-optional-functionality}
102+
103+
To ensure that the end-user experience is consistent across implementations, the specifications mark endpoints and features as Core (required) and Optional.
104+
105+
* Core: The feature or endpoint MUST be supported by all clients and servers.
106+
* Optional: The feature or endpoint is considered to be additional functionality.
107+
108+
Clients and servers may optionally support any combination of these features. Any project implementing Optional functionality should inform users about what is supported.

‎src/content/docs/specs/index.md‎

Lines changed: 8 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,17 @@
11
---
22
title: API reference
33
description: The Open Podcast API is a standard that facilitates the synchronization of podcast data between podcast clients.
4-
sidebar:
5-
order: 1
64
---
75

8-
## 1. Overview
6+
:::caution[Important]
7+
All specifications are currently "in progress". Breaking changes can occur as we implement specifications and address issues.
8+
:::
99

10-
The Open Podcast API is a standard that facilitates the synchronization of podcast data between podcast clients.
10+
This is the API reference for the Open Podcast API. See [conventions](/specs/conventions) for an overview of conventions used for all endpoints. See [authentication](/specs/authentication) for the authentication flow used to authenticate users.
1111

12-
This specification aims to provide comprehensive instructions for client and server developers looking to support the standard.
12+
Below you can find the specifications which are already available. We encourage all interested projects offering podcast listening and/or synchronization functionality to adopt and implement defined specifications. We also welcome feedback on these specifications.
1313

14+
## Core endpoints {#core-endpoints}
1415

15-
## 2. Conventions
16-
17-
### 2.1 Normative language
18-
19-
The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in RFC 2119[^1].
20-
21-
The following terms are also used throughout the documents on this site:
22-
23-
Client
24-
: Software that sends HTTP requests to a conforming server.
25-
26-
Server
27-
: An implementation that exposes the endpoints defined in this specification.
28-
29-
User
30-
: The authenticated principal performing requests.
31-
32-
Feed
33-
: A shared resource representing a podcast feed.
34-
35-
Subscription
36-
: A user-owned resource containing details about a User's subscription to a Feed.
37-
38-
Action
39-
: An operation performed against a subscription resource.
40-
41-
Cursor
42-
: An opaque token used to resume synchronization.
43-
44-
### 2.2 Timestamp format
45-
46-
Timestamps MUST be conform to RFC 3339[^2] and be submitted in UTC.
47-
48-
### 2.3 Data Serialization
49-
50-
All request and response bodies MUST be encoded as UTF-8 JSON.
51-
52-
### 2.4 Identifier formats
53-
54-
This specification uses the following identifier formats:
55-
56-
* UUID version 4 for client identifiers as defined in RFC 9562[^3]
57-
* UUID version 5 for deterministic resource identifiers as defined in RFC 9562[^3]
58-
59-
### 2.5 Core and Optional endpoints
60-
61-
To ensure that the end-user experience is consistent across implementations, the specifications mark endpoints and features as Core (required) and Optional.
62-
63-
Core
64-
: The feature or endpoint MUST be supported by all clients and servers.
65-
66-
Optional
67-
: The feature or endpoint is considered to be additional functionality. Clients and servers MAY optionally support any combination of these features. Any project implementing Optional functionality SHOULD inform users about what is supported.
68-
69-
## 3. Core endpoints
70-
71-
- [Subscriptions](/specs/subscriptions)
72-
73-
[^1]: https://www.rfc-editor.org/rfc/rfc2119
74-
[^2]: https://www.rfc-editor.org/rfc/rfc3339
75-
[^3]: https://www.rfc-editor.org/rfc/rfc9562
16+
* [Synchronization endpoint](/specs/sync)
17+
* [Subscriptions endpoint](/specs/subscriptions)

0 commit comments

Comments
 (0)