Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Project overview

The Open Podcast API project is inspired by and builds upon the gPodder API. In the summer of 2019, the creator of gPodder announced they were stepping down and the community took over.1 Unfortunately, about a year later the project entered 'basic maintenance mode' due to shifting priorities of key contributors. The limited availability of volunteers combined with gPodder.net's popularity among end-users meant that people started to see server errors while synchronizing or creating an account.2 3 Attempts to establish contact and collaborate on improving the situation didn't work out as hoped. Given the situation, AntennaPod contributors started discussing whether gPodder.net support should be removed4 or whether it could be forked. They concluded that the best solution would be to create a new API spec with a broad range of contributors to allow users to switch servers (avoiding major loads on a single server or project), to provide an opportunity to more easily go beyond the existing gPodder.net API specs, and to enable developers to address some technical issues with the API specs.

The initial discussions on GitHub led to a meeting in October 2022 with contributors from AntennaPod, Funkwhale, Kasts, Podfriend and the gPodder app for Nextcloud.5 A few months later, the first of the recurring meetings took place to start developing the specification.

Join us!

We encourage you to engage in the discussions, and provide feedback based on your implementation. See the contribution page how you can get involved.

Code of Conduct

The Open Podcast API project abides by the Contributor Covenant Code of Conduct. Please familiarize yourself with it before participating in any of our community spaces.

Licensing

Our project builds on and is itself open source:

  • Documentation: Creative Commons Attribution-ShareAlike 4.0 International Public License
  • Reference implementations: MIT

  1. Call on the gPodder.net repository for a new maintainer

  2. Questions about gPodder.net issues on the AntennaPod forum

  3. Issue in the gPodder.net repository on 502 errors

  4. Suggestion on the AntennaPod forum to remove gPodder.net support

  5. Initial discussions on the needs for a new podcast synchronisation API

Contribute

The Open Podcast API initiative builds on open source and open standards. Contributors from different projects work together to develop the specifications in the open.

We invite all projects and individuals to join us in developing these specifications. This ensures that we take all perspectives into account and create a set of interoperable specs.

Contributor Covenant Code of Conduct

Our Pledge

We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.

Our Standards

Examples of behavior that contributes to a positive environment for our community include:

  • Demonstrating empathy and kindness toward other people
  • Being respectful of differing opinions, viewpoints, and experiences
  • Giving and gracefully accepting constructive feedback
  • Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
  • Focusing on what is best not just for us as individuals, but for the overall community

Examples of unacceptable behavior include:

  • The use of sexualized language or imagery, and sexual attention or advances of any kind
  • Trolling, insulting or derogatory comments, and personal or political attacks
  • Public or private harassment
  • Publishing others' private information, such as a physical or email address, without their explicit permission
  • Other conduct which could reasonably be considered inappropriate in a professional setting

Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.

Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.

Scope

This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement via conduct AT openpodcastapi DOT org. All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the reporter of any incident.

Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:

1. Correction

Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.

Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.

2. Warning

Community Impact: A violation through a single incident or series of actions.

Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.

3. Temporary Ban

Community Impact: A serious violation of community standards, including sustained inappropriate behavior.

Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.

4. Permanent Ban

Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.

Consequence: A permanent ban from any sort of public interaction within the community.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.

Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder.

For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.

Conventions

Warning

All specifications are currently 'in progress'. Breaking changes can occur as we implement specifications and address issues.

Follow these conventions when contributing to the specs.

Keywords

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in BCP 14 RFC2119 RFC8174 when, and only when, they appear in all capitals, as shown here.

Core and optional functionality

To ensure that the end-user experience is consistent across implementations, the specifications mark endpoints and features as Core (required) and Optional.

  • Core: The feature or endpoint MUST be supported by all clients and servers.
  • Optional: 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.

Headers

The Open Podcast API follows JSON:API media type requirements. The following headers apply to all requests and responses:

Request headers

HeaderRequiredValueDescription
Content-TypeYesapplication/vnd.api+jsonMUST be used when sending data to the server.
AcceptYesapplication/vnd.api+jsonMUST be used to indicate the client expects JSON:API-compliant responses.

Response headers

HeaderRequiredValueDescription
Content-TypeYesapplication/vnd.api+jsonMUST be present in all JSON:API responses.
LocationYes (201)URI of created resourceMUST be included in 201 Created responses to identify the new resource.

Pagination

Clients MAY paginate resource collections using the page query parameter object.

Request format

The server MUST support pagination using the following query parameters:

ParameterTypeDescription
page[number]intThe page of results to return. Defaults to 1.
page[size]intThe number of results to return per page. Defaults to 25, max: 100.

Example:

GET /v1/subscriptions?page[number]=2&page[size]=50

Response format

The server MUST include the following in paginated responses:

The top-level links object MUST include pagination links using the following keys:

Link keyDescription
selfThe current page's full request URI.
firstThe URI for the first page of results.
prevThe URI for the previous page, or null if the current page is first.
nextThe URI for the next page, or null if the current page is last.
lastThe URI for the last page of results.

Example:

"links": {
  "self": "/v1/subscriptions?page[number]=2&page[size]=25",
  "first": "/v1/subscriptions?page[number]=1&page[size]=25",
  "prev": "/v1/subscriptions?page[number]=1&page[size]=25",
  "next": "/v1/subscriptions?page[number]=3&page[size]=25",
  "last": "/v1/subscriptions?page[number]=4&page[size]=25"
}

meta

The top-level meta object MAY include pagination metadata:

FieldTypeDescription
totalintTotal number of items available (optional).

Example:

"meta": {
  "total": 100
}

Server behavior

  • The server SHOULD enforce a maximum page size (e.g., 100 items).
  • The server MUST return an empty data array if the page is valid but contains no items.
  • The server MAY omit the total field in meta if computing it is expensive.

Compatibility

This pagination model is compliant with the JSON:API pagination specification.

Error codes

This page lists the common HTTP status codes used across the API. All error responses MUST conform to the JSON:API error object format.

Success status codes

200 OK

The request was successfully processed. This status is typically used for GET, PATCH, or bulk POST operations when resources are modified or retrieved but not newly created.

201 Created

The request created a new resource. The response will include a Location header with the URI of the created resource and a data object describing it.

Client error status codes

400 Bad Request

The request was malformed, invalid JSON, or structurally incorrect according to the JSON:API format. This may include missing required members such as type, id, or attributes.

401 Unauthorized

Authentication is required and has failed or was not provided. The client MUST provide valid credentials to access the endpoint.

403 Forbidden

The client is authenticated but does not have permission to perform the requested action.

404 Not Found

The requested resource does not exist, or the URL is invalid.

405 Method Not Allowed

The HTTP method used is not supported for the target endpoint.

409 Conflict

The request could not be completed due to a conflict with the current state of the resource. For example, attempting to create a resource that already exists and cannot be duplicated.

415 Unsupported Media Type

The Content-Type of the request must be application/vnd.api+json. This error is returned if the client uses an incorrect or missing media type.

422 Unprocessable Entity

The request is syntactically valid, but semantically invalid or fails domain-specific validation rules.

Server error status codes

500 Internal Server Error

A generic error indicating an unexpected server-side condition. Clients SHOULD NOT rely on this for predictable error handling.


Error object format

All error responses MUST return an array of error objects using the format defined by JSON:API.

{
  "errors": [
    {
      "status": "422",
      "title": "invalid field value",
      "detail": "the provided guid is not a valid UUIDv5",
      "source": { "pointer": "/data/id" },
      "meta": {
        "provided_guid": "1234-invalid-guid"
      }
    }
  ]
}

Required members

  • status: the HTTP status code
  • title: a short summary of the error
  • detail: a longer human-readable explanation of the issue

Optional members

  • source.pointer: a JSON Pointer to the part of the request that caused the error
  • meta: additional non-standard metadata useful for debugging or client-side handling

Subscriptions

Core endpoint

This is a core endpoint. All server implementations MUST support it.

Overview

A subscription represents a podcast feed a user has subscribed to. Subscriptions are essential entities that link users to feeds, episodes, and other related data.

The subscriptions endpoint is used to synchronize subscriptions between a server and connected clients.

guid calculation

Subscriptions are identified by a globally unique guid value. This guid is used as the id field of each subscription resource and acts as the canonical resource identifier.

Feed guid values MUST be created in accordance with the Podcast Index's methodology. If a feed already has a valid UUIDv5 guid tag, the client MUST pass this value to the server when submitting the feed. If the feed doesn't have a valid guid tag, the client MUST:

  1. Generate a UUIDv5 guid value using:
    • The feed URL (with protocol and trailing slashes removed).
    • The Podcast namespace UUID: ead4c236-bf58-58c6-a2c6-a6b28d128cb6.
  2. Pass the generated value as the guid in the API request.

This process ensures that any feed not currently registered with the Podcast Index is identified by the exact same GUID it would receive if it were updated to the Podcasting 2.0 specification.

The server MUST reject invalid guid values with a 400 Bad Request response.

The server MUST NOT attempt to validate or recalculate the guid against the feed_url, as the origin URL may have changed since the guid was set.

Supported actions

Add a subscription

Core action

This is a core action. All server implementations MUST support it.

Enables clients to add a new subscription to the system and register the authenticated user as a subscriber.

POST /v1/subscriptions

Request format

The client MUST provide a single resource object with:

  • type: MUST be "subscription"
  • id: MUST be the guid (UUIDv5) for the feed. See guid calculation
  • attributes.feed_url: The podcast feed URL
POST /v1/subscriptions HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "subscription",
    "id": "ce510f4d-9046-5590-846e-58619ab8b353",
    "attributes": {
      "feed_url": "https://example.com/rss1"
    }
  }
}

Server behavior

  • The server MUST create the subscription or confirm the existing record.
  • The operation MUST be idempotent.
  • The server MUST return a 201 Created response with the created resource.
  • If the resource already exists, the server MAY return a 200 OK.

Success response

If the subscription is created or confirmed successfully:

  • HTTP Status: 201 Created
  • Headers:
    • Content-Type: application/vnd.api+json
    • Location: <URI of created resource>

The created resource MUST have the following attributes:

  • feed_url: the feed URL of the subscription target
  • user_subscribed_at: the timestamp at which the user's subscription was created, in ISO 8601 format

The response MUST contain the following links.

  • self: a link to the created resource
  • unsubscribe:
    • href: a link to the created resource
    • method: MUST be DELETE
HTTP/1.1 201 Created
Location: /v1/subscriptions/ce510f4d-9046-5590-846e-58619ab8b353
Content-Type: application/vnd.api+json

{
  "jsonapi": { "version": "1.1" },
  "data": {
    "type": "subscription",
    "id": "ce510f4d-9046-5590-846e-58619ab8b353",
    "attributes": {
      "feed_url": "https://example.com/rss1",
      "user_subscribed_at": "2025-08-24T16:00:00Z"
    },
    "links": {
      "self": "/v1/subscriptions/ce510f4d-9046-5590-846e-58619ab8b353",
      "unsubscribe": {
        "href": "/v1/subscriptions/ce510f4d-9046-5590-846e-58619ab8b353",
        "method": "DELETE"
      }
    }
  }
}

Bulk operations

Clients MAY use the Atomic Operations extension for batch creation or updates. See Add subscriptions in bulk for more information.

List subscriptions

Core action

This is a core action. All server implementations MUST support it.

Retrieves a paginated list of podcast subscriptions associated with the authenticated user.

GET /v1/subscriptions

Request format

This endpoint supports optional query parameters for pagination:

ParameterDescription
page[number]The page of results to return (default: 1).
page[size]The number of results to return per page (default: 25, max: 100).
GET /v1/subscriptions?page[number]=1&page[size]=2 HTTP/1.1
Accept: application/vnd.api+json

Server behavior

  • The server MUST return only subscriptions that belong to the authenticated user.
  • The server MUST return pagination metadata and pagination links.
  • The server MUST return an empty array if the user has no subscriptions.
  • The server SHOULD support page[size] values between 1 and 100.

Success response

  • HTTP Status: 200 OK
  • Content-Type: application/vnd.api+json

Each subscription resource in the response MUST include:

  • type: "subscription"
  • id: the globally unique guid
  • attributes.feed_url: the feed URL
  • attributes.user_subscribed_at: the timestamp of when the user subscribed in ISO 8601 format
  • links.self: link to the subscription resource
  • links.unsubscribe: a DELETE-able link to unsubscribe from the feed

The top-level links object MUST include pagination links:

  • self: the current request URI
  • first: the first page of results
  • prev: the previous page, if one exists
  • next: the next page, if one exists
  • last: the last page of results

The top-level meta object MAY include total count metadata.

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "jsonapi": { "version": "1.1" },
  "data": [
    {
      "type": "subscription",
      "id": "ce510f4d-9046-5590-846e-58619ab8b353",
      "attributes": {
        "feed_url": "https://example.com/rss1",
        "user_subscribed_at": "2025-08-24T16:00:00Z"
      },
      "links": {
        "self": "/v1/subscriptions/ce510f4d-9046-5590-846e-58619ab8b353",
        "unsubscribe": {
          "href": "/v1/subscriptions/ce510f4d-9046-5590-846e-58619ab8b353",
          "method": "DELETE"
        }
      }
    },
    {
      "type": "subscription",
      "id": "b80719b3-1485-57c0-9e55-fda2b8f7472b",
      "attributes": {
        "feed_url": "https://example.com/rss2",
        "user_subscribed_at": "2025-08-24T16:00:00Z"
      },
      "links": {
        "self": "/v1/subscriptions/b80719b3-1485-57c0-9e55-fda2b8f7472b",
        "unsubscribe": {
          "href": "/v1/subscriptions/b80719b3-1485-57c0-9e55-fda2b8f7472b",
          "method": "DELETE"
        }
      }
    }
  ],
  "links": {
    "self": "/v1/subscriptions?page[number]=1&page[size]=2",
    "first": "/v1/subscriptions?page[number]=1&page[size]=2",
    "prev": null,
    "next": "/v1/subscriptions?page[number]=2&page[size]=2",
    "last": "/v1/subscriptions?page[number]=5&page[size]=2"
  },
  "meta": {
    "total": 10
  }
}

Get a subscription

Core action

This is a core action. All server implementations MUST support it.

Enables clients to fetch a single subscription resource by its globally unique guid.

GET /v1/subscriptions/{guid}

Request format

Clients MUST send a GET request to the canonical URI of a subscription resource, using the guid as the identifier.

Path parameters

ParameterTypeDescription
guidstringThe globally unique identifier (UUIDv5) of the subscription resource.

Headers

Clients MUST include the following headers:

  • Accept: application/vnd.api+json
GET /v1/subscriptions/ce510f4d-9046-5590-846e-58619ab8b353 HTTP/1.1
Accept: application/vnd.api+json

Server behavior

  • The server MUST return a 200 OK response with the subscription resource if it exists and is associated with the authenticated user.
  • If the subscription does not exist, the server MUST return a 404 Not Found error object.
  • The server MUST validate that the guid is a valid UUID format. If it is not, the server MUST return a 400 Bad Request.

Success response

If the subscription is found and accessible to the current user:

  • HTTP Status: 200 OK
  • Headers:
    • Content-Type: application/vnd.api+json

The response body MUST include:

  • A top-level data object containing the subscription resource.
  • A links.self pointing to the resource.
  • A links.unsubscribe pointing to the same resource with method: DELETE.
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "jsonapi": { "version": "1.1" },
  "data": {
    "type": "subscription",
    "id": "ce510f4d-9046-5590-846e-58619ab8b353",
    "attributes": {
      "feed_url": "https://example.com/rss1",
      "user_subscribed_at": "2025-08-24T16:00:00Z"
    },
    "links": {
      "self": "/v1/subscriptions/ce510f4d-9046-5590-846e-58619ab8b353",
      "unsubscribe": {
        "href": "/v1/subscriptions/ce510f4d-9046-5590-846e-58619ab8b353",
        "method": "DELETE"
      }
    }
  }
}

Error responses

HTTP statusWhen it occurs
400 Bad RequestIf the provided guid is not a valid UUID.
404 Not FoundIf the subscription does not exist or is inaccessible.

See error response format for more information.

Delete a subscription

Core action

This is a core action. All server implementations MUST support it.

Enables clients to remove a subscription for the authenticated user by guid.

DELETE /v1/subscriptions/{guid}

Request format

Clients MUST send a DELETE request to the canonical URI of a subscription resource.

Path parameters

ParameterTypeDescription
guidstringThe globally unique identifier (UUIDv5) of the subscription resource.

Headers

Clients MUST include the following header:

  • Accept: application/vnd.api+json
DELETE /v1/subscriptions/ce510f4d-9046-5590-846e-58619ab8b353 HTTP/1.1
Accept: application/vnd.api+json

Server behavior

  • The server MUST remove the subscription for the authenticated user.
  • The deletion MUST NOT affect other users subscribed to the same feed.
  • The server MUST return a 204 No Content if the subscription was deleted.
  • If no subscription exists for the user, the server MUST return 404 Not Found.
  • If the guid is not a valid UUID, the server MUST return 400 Bad Request.

Success response

If the subscription is successfully deleted:

  • HTTP Status: 204 No Content
  • The response MUST NOT include a body.
HTTP/1.1 204 No Content

Error responses

HTTP statusWhen it occurs
400 Bad RequestIf the provided guid is not a valid UUID.
404 Not FoundIf the subscription does not exist or is inaccessible.

See error response format for more information.

Operations endpoint

Core endpoint

This is a core endpoint. All server implementations MUST support it.

The operations endpoint allows clients to perform multiple resource-level operations in a single, ordered request. It follows the JSON:API Atomic Operations extension and is intended for use cases where bulk writes or tightly ordered changes are needed.

POST /v1/operations

Request format

Clients MUST send a JSON document with an atomic:operations key containing an array of operation objects. Each operation object MUST specify:

  • op: the operation type (e.g. "add", "update", "remove")
  • data: the resource object for the operation

Each operation MUST include a resource object with:

  • type: the resource type (e.g. "subscription")
  • id: the resource identifier, if applicable
  • attributes: resource attributes as appropriate for the action

Headers

Clients MUST set the following headers:

  • Content-Type: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"
  • Accept: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"
POST /v1/operations HTTP/1.1
Content-Type: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"

Example request

{
  "atomic:operations": [
    {
      "op": "add",
      "data": {
        "type": "subscription",
        "id": "b80719b3-1485-57c0-9e55-fda2b8f7472b",
        "attributes": {
          "feed_url": "https://example.com/rss2"
        }
      }
    },
    {
      "op": "add",
      "data": {
        "type": "subscription",
        "id": "d29519a4-77ee-5e13-bb14-89fc93b993ae",
        "attributes": {
          "feed_url": "https://example.com/rss3"
        }
      }
    }
  ]
}

Server behavior

  • The server MUST process operations in order.
  • Each operation MUST be executed independently.
  • The server MUST return a 200 OK response even if one or more operations fail.
  • The response MUST include an atomic:results array with the same number of elements as the input.
  • Each results element corresponds positionally to its respective request operation.

Success response format

For successful operations, the corresponding item in atomic:results MUST contain a data object.

Error response format

If an operation fails, the corresponding item MUST contain an errors array, conforming to the JSON:API error object format.

Response headers

The server MUST set the following headers:

  • Content-Type: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"

Supported operations

The operations endpoint may be used for:

Additional bulk actions (e.g. update, remove) may be supported in the future. Clients MUST only use operations documented for this server.

Errors

For error semantics and structure, see error responses.

Errors may occur for:

  • Validation failures (422)
  • Resource conflicts (409)
  • Type mismatches (409 or 400)
  • Invalid resource identifiers (400)

All errors are returned inline within the atomic:results array.

See also

Add subscriptions in bulk

Core action

This is a core action. All server implementations MUST support it.

POST /v1/operations

Creates multiple subscription resources. See Subscriptions for more information.

Request

Clients MUST send an array of operations, each specifying:

  • op: "add"
  • data: MUST contain a single resource object

Example

POST /api/v1/operations HTTP/1.1
Content-Type: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"
Accept: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"

{
  "atomic:operations": [
    {
      "op": "add",
      "data": {
        "type": "subscription",
        "id": "ce510f4d-9046-5590-846e-58619ab8b353",
        "attributes": {
          "feed_url": "https://example.com/rss1"
        }
      }
    },
    {
      "op": "add",
      "data": {
        "type": "subscription",
        "id": "b80719b3-1485-57c0-9e55-fda2b8f7472b",
        "attributes": {
          "feed_url": "https://example.com/rss2"
        }
      }
    }
  ]
}

Server behavior

  • The server MUST attempt to perform each operation independently.
  • Operations MUST be executed in order.
  • The response MUST contain a results array with a one-to-one mapping of the input operations.
  • If any operation fails, the corresponding results entry MUST be an error object.
  • The server MUST return 200 OK regardless of partial failures.

Success response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"

{
  "atomic:results": [
    {
      "data": {
        "type": "subscription",
        "id": "ce510f4d-9046-5590-846e-58619ab8b353",
        "attributes": {
          "feed_url": "https://example.com/rss1",
          "user_subscribed_at": "2025-08-24T16:00:00Z"
        },
        "links": {
          "self": "/v1/subscriptions/ce510f4d-9046-5590-846e-58619ab8b353"
        }
      }
    },
    {
      "data": {
        "type": "subscription",
        "id": "b80719b3-1485-57c0-9e55-fda2b8f7472b",
        "attributes": {
          "feed_url": "https://example.com/rss2",
          "user_subscribed_at": "2025-08-24T16:00:00Z"
        },
        "links": {
          "self": "/v1/subscriptions/b80719b3-1485-57c0-9e55-fda2b8f7472b"
        }
      }
    }
  ]
}

Mixed success response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json;ext="https://jsonapi.org/ext/atomic"

{
  "atomic:results": [
    {
      "data": {
        "type": "subscription",
        "id": "ce510f4d-9046-5590-846e-58619ab8b353",
        "attributes": {
          "feed_url": "https://example.com/rss1",
          "user_subscribed_at": "2025-08-24T16:00:00Z"
        }
      }
    },
    {
      "errors": [
        {
          "status": "409",
          "title": "Already Subscribed",
          "detail": "User is already subscribed to this feed.",
          "source": { "pointer": "/atomic:operations/1" },
          "meta": {
            "feed_url": "https://example.com/rss2",
            "guid": "b80719b3-1485-57c0-9e55-fda2b8f7472b"
          }
        }
      ]
    }
  ]
}