Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export default defineConfig({
label: "Introduction",
link: "specs",
},
{
label: "Capabilities",
collapsed: true,
autogenerate: {
directory: "specs/capabilities",
},
},
{
label: "Subscriptions",
collapsed: true,
Expand Down
58 changes: 57 additions & 1 deletion schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,20 @@ paths:
security:
- podcast_auth:
- read:subscriptions
/capabilities:
get:
tags:
- Capabilities
summary: Retrieve server capabilities
description: Returns a summary of features and core specification versions supported by the server.
operationId: getCapabilities
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: "#/components/schemas/Capabilities"
components:
responses:
Unauthorized:
Expand Down Expand Up @@ -573,6 +587,48 @@ components:
deletion_id: 25
status: SUCCESS
message: Subscription deleted successfully
Capabilities:
type: object
properties:
capabilities:
type: object
description: |
The features supported by the server. The first object MUST be "urn:opa:core".
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "first object" reads strangely. "urn:opa:core" is a string, not an object. Furthermore, capabilities is an object, not a list. I've never seen a JSON api which makes any promises/requirements about the order of keys in an object. I wouldn't be surprised if there are programming languages with popular JSON libraries which do not preserve the order of keys in a JSON object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jfly Good point, it'll be directly destructured either way so position isn't important. I'll update that.

additionalProperties:
type: object
description: A map of versions for a specific feature.
additionalProperties:
type: object
required:
- status
- root
properties:
status:
type: string
enum: [STABLE, UNSTABLE, DEPRECATED]
root:
type: string
example:
urn:opa:core:
"0.0.1":
status: DEPRECATED
root: '/v0'
"1.0.0":
status: STABLE
root: '/v1'
"2.0.0":
status: UNSTABLE
root: '/v2'
urn:opa:extra:playcount:
"0.0.1":
status: DEPRECATED
root: '/v0/playcount'
"1.0.0":
status: STABLE
root: '/v1/playcount'
"2.0.0":
status: UNSTABLE
root: '/v2/playcount'
requestBodies:
FeedArray:
description: An array of feeds the user wants to subscribe to
Expand Down Expand Up @@ -604,4 +660,4 @@ components:
api_key:
type: apiKey
name: api_key
in: header
in: header
58 changes: 58 additions & 0 deletions src/content/docs/specs/capabilities/get-capabilities.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: Get capabilities
description: An endpoint for querying server capabilities
next: false
sidebar:
order: 2
---

import CoreAction from "@partials/_core-action.mdx";

<CoreAction />

```http title="Endpoint"
GET /v1/capabilities
```

The capabilities endpoint exposes details about the features a server supports. Capabilities are scoped by `urn` and MUST contain the following:

- Each supported version of the feature.
- The `status` of the feature version: `STABLE` | `UNSTABLE` | `DEPRECATED`.
- The `root` relative URL the feature can be accessed at.

Each `capabilities` response MUST contain a `urn:opa:core` object that lists the versions of the Open Podcast API the server supports. Additional capabilities MAY be returned as separate `urn` objects with supported versions.

## Example 200 response

```json
{
"urn:opa:core": {
"0.0.1": {
"status": "DEPRECATED",
"root": "/v0"
},
"1.0.0": {
"status": "STABLE",
"root": "/v1"
},
"2.0.0": {
"status": "UNSTABLE",
"root": "/v2"
}
},
"urn:opa:extra:playcount": {
"0.0.1": {
"status": "DEPRECATED",
"root": "/v0/playcount"
},
"1.0.0": {
"status": "STABLE",
"root": "/v1/playcount"
},
"2.0.0": {
"status": "UNSTABLE",
"root": "/v2/playcount"
}
}
}
```
29 changes: 29 additions & 0 deletions src/content/docs/specs/capabilities/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Capabilities endpoint
description: An endpoint for querying server capabilities
prev: false
sidebar:
label: Overview
order: 1
---

import CoreEndpoint from "@partials/_core-endpoint.mdx";

<CoreEndpoint />

The Open Podcast API is subject to change over time. To ensure backwards compatibility and effective communication of server capabilities, the API specification MUST be versioned in a consistent way.

The specification follows the [Semantic versioning model](https://semver.org/). Each version of the specification MUST adhere to the following rules:

**Major** versions
: A major version update indicates that a breaking change has occurred. This is reserved for the deprecation, addition, or renaming of **core** capabilities.

**Minor** versions
: A minor version update indicates that a new **optional** feature has been added or that a **core** feature has received a non-breaking change. This can include the deprecation or addition of parameters or behaviors.

**Patch** versions
: A patch version update indicates that a small non-breaking change has been made to clarify a feature or address an issue with wording.

## Backwards compatibility

To maintain backwards compatibility between **minor** versions, no parameters nor endpoints may be removed without a **major** version change. Fields may be deprecated in favor of new behaviors, but when queried by an older client the server MUST respond with a compatible response.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when queried by an older client

Sorry, I have not read the existing spec. Is there a mechanism for servers to know if they're dealing with an older client? That would be nice so people developing new clients don't accidebtally use deprecated request/response fields.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jfly In general, the idea is that server developers should scope their implementation to a specific root. This means that if they support version 1.0.0 and version 2.0.0, they should put these on different roots (e.g. /api/v1 for 1.0.0 and /api/v2 for 2.0.0). This endpoint should then inform the client where the root is for the supported version.

The current approach doesn't programatically filter anything out, and I'm wondering if HATEOAS would be a good approach here for exposing this information in a more structured way. We could update this endpoint to require support for query parameters indicating the maximum and minimum versions of the core spec a client supports. That way, the client can ask the server if its capabilities are supported and use the latest supported version if so, but if not the client can report back. For example:

  1. A client supporting v1.0.0 to v1.5.0 reaches out to a server and requests capabilities with a min and max parameter. For example GET https://openpodcastapi.org/api/capabilities?min=1.0.0&max=1.5.0.

  2. The server responds with a payload showing ONLY supported core versions within that range. Extra features would still be listed in full because the core is the only thing we care about.

    {
      "urn:opa:core": {
        "1.0.0": {
          "status": "STABLE",
          "root": "/api/v1"
        }
      },
      "urn:opa:extra:playcount": {
        "0.0.1": {
          "status": "DEPRECATED",
          "root": "/api/v0/playcount"
        },
        "1.0.0": {
          "status": "STABLE",
          "root": "/api/v1/playcount"
        },
        "2.0.0": {
          "status": "UNSTABLE",
          "root": "/api/v2/playcount"
        }
      }
    }
  3. The client then knows to only use the /v1 endpoint and to make use of methods available in the v1.0.0 specification.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My initial reaction is that this is puts complexity in 2 places:

  • The server needs to parse the min + max paramterers, and filter the supported versions in its urn:opa:core response
  • The client still needs to pick a core version from the capabilities response, and versions from any extra features it supports as well.

Would it be better to put all the logic in the client: just request /capabilities, and leave it up to the client to decide which version of the core + extra versions to use? It seems to me like clients will still have the same amount of logic, but this simplifies the api and the server side a bit.