



Network Working Group                                 Trimplayer Editors
Internet-Draft                                                Trimplayer
Intended status: Informational                               28 May 2026
Expires: 29 November 2026


  PortCast: A JSON-Based Interchange Format and Sync API for Portable
                         Podcast Listener Data
                      draft-trimplayer-portcast-00

Abstract

   PortCast defines an open JSON-based interchange format, and an
   optional HTTPS synchronisation API, for moving a podcast listener's
   data -- subscriptions, listening history, playback position, queue,
   bookmarks, and per-feed preferences -- between independent podcast
   applications without a central service.  It builds on identifiers
   already present in RSS (item GUID, feed URL) and the Podcast
   Namespace (podcast:guid) so that implementations can interoperate
   without inventing a new identity namespace.  This document specifies
   the file format (v0.1) and a federated synchronisation API (v0.2)
   that reuses the same data model.

About This Document

   This note is to be removed before publishing as an RFC.

   The latest revision of this draft can be found at
   https://portcast.org/.  Status information for this document may be
   found at https://datatracker.ietf.org/doc/draft-trimplayer-portcast/.

   Source for this draft and an issue tracker can be found at
   https://github.com/Trim-Player/PortCast.

Status of This Memo

   This Internet-Draft is submitted in full conformance with the
   provisions of BCP 78 and BCP 79.

   Internet-Drafts are working documents of the Internet Engineering
   Task Force (IETF).  Note that other groups may also distribute
   working documents as Internet-Drafts.  The list of current Internet-
   Drafts is at https://datatracker.ietf.org/drafts/current/.

   Internet-Drafts are draft documents valid for a maximum of six months
   and may be updated, replaced, or obsoleted by other documents at any
   time.  It is inappropriate to use Internet-Drafts as reference
   material or to cite them other than as "work in progress."



Trimplayer Editors      Expires 29 November 2026                [Page 1]

Internet-Draft                  PortCast                        May 2026


   This Internet-Draft will expire on 29 November 2026.

Copyright Notice

   Copyright (c) 2026 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents (https://trustee.ietf.org/
   license-info) in effect on the date of publication of this document.
   Please review these documents carefully, as they describe your rights
   and restrictions with respect to this document.

Table of Contents

   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   3
     1.1.  Design goals  . . . . . . . . . . . . . . . . . . . . . .   3
   2.  Terminology and conformance . . . . . . . . . . . . . . . . .   4
     2.1.  Requirements language . . . . . . . . . . . . . . . . . .   4
     2.2.  Defined roles . . . . . . . . . . . . . . . . . . . . . .   4
   3.  Document container  . . . . . . . . . . . . . . . . . . . . .   5
   4.  Identity model  . . . . . . . . . . . . . . . . . . . . . . .   6
     4.1.  Podcast identity  . . . . . . . . . . . . . . . . . . . .   6
     4.2.  Episode identity  . . . . . . . . . . . . . . . . . . . .   7
   5.  Subscriptions . . . . . . . . . . . . . . . . . . . . . . . .   7
   6.  Episode state . . . . . . . . . . . . . . . . . . . . . . . .   8
     6.1.  status values . . . . . . . . . . . . . . . . . . . . . .   8
     6.2.  Playback events . . . . . . . . . . . . . . . . . . . . .   9
   7.  Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . .   9
   8.  Bookmarks . . . . . . . . . . . . . . . . . . . . . . . . . .   9
   9.  Preferences . . . . . . . . . . . . . . . . . . . . . . . . .   9
   10. Extensions  . . . . . . . . . . . . . . . . . . . . . . . . .  10
   11. Versioning  . . . . . . . . . . . . . . . . . . . . . . . . .  10
   12. Live sync API (v0.2 -- Draft) . . . . . . . . . . . . . . . .  11
     12.1.  Operating modes  . . . . . . . . . . . . . . . . . . . .  11
     12.2.  Discovery  . . . . . . . . . . . . . . . . . . . . . . .  11
     12.3.  Versioning and content type  . . . . . . . . . . . . . .  12
     12.4.  Authentication . . . . . . . . . . . . . . . . . . . . .  12
     12.5.  Endpoints  . . . . . . . . . . . . . . . . . . . . . . .  13
     12.6.  Delta sync . . . . . . . . . . . . . . . . . . . . . . .  14
     12.7.  Pagination . . . . . . . . . . . . . . . . . . . . . . .  14
     12.8.  Conditional updates  . . . . . . . . . . . . . . . . . .  14
     12.9.  Errors . . . . . . . . . . . . . . . . . . . . . . . . .  14
     12.10. Webhooks (optional)  . . . . . . . . . . . . . . . . . .  15
     12.11. Capability fallback  . . . . . . . . . . . . . . . . . .  15
     12.12. Federation . . . . . . . . . . . . . . . . . . . . . . .  15
   13. Security considerations . . . . . . . . . . . . . . . . . . .  16
     13.1.  Sensitivity of the data  . . . . . . . . . . . . . . . .  16



Trimplayer Editors      Expires 29 November 2026                [Page 2]

Internet-Draft                  PortCast                        May 2026


     13.2.  Producer requirements  . . . . . . . . . . . . . . . . .  16
     13.3.  Consumer requirements  . . . . . . . . . . . . . . . . .  16
     13.4.  Transport and storage (API mode) . . . . . . . . . . . .  17
     13.5.  Threat model and out-of-scope risks  . . . . . . . . . .  17
   14. IANA considerations . . . . . . . . . . . . . . . . . . . . .  17
     14.1.  Media type registration  . . . . . . . . . . . . . . . .  17
     14.2.  Well-known URI registration  . . . . . . . . . . . . . .  19
     14.3.  OAuth 2.0 scope registration . . . . . . . . . . . . . .  19
     14.4.  PortCast error code registry . . . . . . . . . . . . . .  20
   15. References  . . . . . . . . . . . . . . . . . . . . . . . . .  21
     15.1.  Normative References . . . . . . . . . . . . . . . . . .  21
     15.2.  Informative References . . . . . . . . . . . . . . . . .  23
   Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . .  23
   Author's Address  . . . . . . . . . . . . . . . . . . . . . . . .  23

1.  Introduction

   A listener's relationship with their podcasts -- which shows they
   follow, where they stopped in an unfinished episode, the clip they
   bookmarked at 23:04 -- currently lives inside whichever application
   they happen to use.  Switching applications restarts that
   relationship from zero.  OPML [OPML2.0] solves the subscription case,
   but everything else (playback position, completion state, queue,
   bookmarks, per-feed preferences) is lost on every migration.

   This document specifies PortCast, a protocol whose goal is simple: a
   listener SHOULD be able to leave any podcast application and arrive
   at any other application with the relationship to their podcasts
   intact.  PortCast defines a JSON document format (file mode,
   Section 3 through Section 10) and an optional HTTPS API (API mode,
   Section 12) that exposes the same entities for incremental
   synchronisation.  The two modes share a single data model; file mode
   is the interoperability floor that every conforming implementation
   can fall back to.

   PortCast is intentionally federated.  There is no central directory,
   registry, or authority.  Each application exposes its own endpoint on
   its own domain, or produces its own files.  The editors of this
   specification commit to not operating a central service.

1.1.  Design goals

   1.  *Listener-owned.* The document is produced by the user, for the
       user.  No vendor lock-in, no proprietary identifiers required.







Trimplayer Editors      Expires 29 November 2026                [Page 3]

Internet-Draft                  PortCast                        May 2026


   2.  *Interoperable identity.* Use open identifiers already present in
       RSS (item GUID, feed URL) rather than inventing a new namespace.
       Implementations MAY add their own identifiers in a namespaced
       extension block.

   3.  *Lossless within the model.* A conforming export captures
       everything the protocol defines.  Anything outside the model goes
       in extensions so it round-trips through implementations that do
       not understand it.

   4.  *Partial and incremental.* Every entity carries an updatedAt
       timestamp, so a future synchronisation profile can ship deltas.
       The v0.1 file format is a full snapshot, but the data model is
       synchronisation-friendly.

   5.  *Human-readable.* A listener SHOULD be able to open the file in a
       text editor and recognise what it says about them.

   6.  *Versioned.* The document declares its protocol version;
       implementations can negotiate behaviour.

2.  Terminology and conformance

2.1.  Requirements language

   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.

2.2.  Defined roles

   A *producer* is software that writes a PortCast document or serves
   PortCast API responses.

   A *consumer* is software that reads a PortCast document or calls a
   PortCast API.

   A *conforming producer* MUST write a document that validates against
   the JSON Schemas published with this specification.  A *conforming
   consumer* MUST accept any document that validates against those
   schemas, and MUST NOT reject a document because of unrecognised keys
   inside an extensions object (Section 10).







Trimplayer Editors      Expires 29 November 2026                [Page 4]

Internet-Draft                  PortCast                        May 2026


3.  Document container

   A PortCast document is a single JSON object [RFC8259], encoded in
   UTF-8 without a byte order mark, with the following top-level shape:

   {
     "portcast": "0.1.0",
     "generatedAt": "2026-05-26T14:00:00Z",
     "generator": { "name": "Trimplayer", "version": "3.4.1" },
     "owner": { "displayName": "Jonathan", "email": "user@example.com" },
     "subscriptions": [ ],
     "episodes":      [ ],
     "queue":         [ ],
     "bookmarks":     [ ],
     "preferences":   { },
     "extensions":    { }
   }


































Trimplayer Editors      Expires 29 November 2026                [Page 5]

Internet-Draft                  PortCast                        May 2026


    +===============+==========+=====================================+
    | Field         | Required | Notes                               |
    +===============+==========+=====================================+
    | portcast      | yes      | Semantic-versioned string.  The     |
    |               |          | version of this spec.               |
    +---------------+----------+-------------------------------------+
    | generatedAt   | yes      | RFC 3339 [RFC3339] timestamp in     |
    |               |          | UTC.                                |
    +---------------+----------+-------------------------------------+
    | generator     | yes      | Producing application identifier.   |
    +---------------+----------+-------------------------------------+
    | owner         | no       | Optional listener identity.         |
    |               |          | Producers SHOULD let users opt out. |
    +---------------+----------+-------------------------------------+
    | subscriptions | yes      | Array of Subscription.  MAY be      |
    |               |          | empty.                              |
    +---------------+----------+-------------------------------------+
    | episodes      | yes      | Array of EpisodeState.  MAY be      |
    |               |          | empty.                              |
    +---------------+----------+-------------------------------------+
    | queue         | no       | Ordered array of QueueItem.         |
    +---------------+----------+-------------------------------------+
    | bookmarks     | no       | Array of Bookmark.                  |
    +---------------+----------+-------------------------------------+
    | preferences   | no       | Preferences object.                 |
    +---------------+----------+-------------------------------------+
    | extensions    | no       | Namespaced extension data           |
    |               |          | (Section 10).                       |
    +---------------+----------+-------------------------------------+

                                 Table 1

   The file extension SHOULD be .portcast.json and the IANA media type
   SHOULD be application/vnd.portcast+json (see Section 14).

4.  Identity model

   Identity is the heart of an interoperability protocol.  PortCast
   identifies entities at two levels.

4.1.  Podcast identity

   A Subscription MUST carry at least one of:

   *  feedUrl -- the canonical RSS or Atom URL of the show.






Trimplayer Editors      Expires 29 November 2026                [Page 6]

Internet-Draft                  PortCast                        May 2026


   *  podcastGuid -- the Podcast Namespace <podcast:guid> value
      [PODCAST-NAMESPACE] when the feed publishes one.  This is the
      strongest identifier and SHOULD be preferred when matching across
      applications.

   A subscription SHOULD include both when both are available.
   Producers MAY also carry directory-specific identifiers (e.g., Apple
   Podcasts, Podcast Index) under Subscription.identifiers.*; these are
   advisory and not required for matching.

4.2.  Episode identity

   An EpisodeState MUST carry at least one of:

   *  guid -- the RSS <item><guid> value (preferred).

   *  enclosureUrl -- the media URL from <enclosure url="...">.

   It MUST also carry a subscriptionRef (either podcastGuid or feedUrl)
   that matches one of the document's subscriptions[] entries.  If
   neither a guid nor an enclosureUrl is known, an episode state MAY use
   a stable (subscriptionRef, publishedAt, title) tuple, but consumers
   are not required to match by it.

5.  Subscriptions

   {
     "subscriptionId": "01HXYZ...",
     "feedUrl": "https://example.com/feed.xml",
     "podcastGuid": "917393e3-1b1e-5cef-ace4-edaa54e1f810",
     "title": "Example Podcast",
     "author": "Jane Doe",
     "imageUrl": "https://example.com/cover.jpg",
     "subscribedAt": "2024-06-01T09:14:00Z",
     "unsubscribedAt": null,
     "tags": ["tech", "weekly-listen"],
     "notificationsEnabled": true,
     "identifiers": {
       "applePodcastsId": "1500000000",
       "podcastIndexId": "920666"
     },
     "updatedAt": "2026-05-26T14:00:00Z"
   }

   unsubscribedAt is set when the listener has stopped following the
   show but the producer still wishes to convey historical context.
   Consumers MAY discard unsubscribed entries on import. tags are free-
   form, listener-applied labels.



Trimplayer Editors      Expires 29 November 2026                [Page 7]

Internet-Draft                  PortCast                        May 2026


6.  Episode state

   {
     "episodeStateId": "01HXYZ...",
     "subscriptionRef": { "podcastGuid": "917393e3-..." },
     "guid": "https://example.com/ep/42",
     "enclosureUrl": "https://example.com/audio/ep42.mp3",
     "title": "Episode 42",
     "publishedAt": "2026-05-20T07:00:00Z",
     "durationSeconds": 3287,

     "status": "in_progress",
     "positionSeconds": 1245.2,
     "playCount": 1,
     "completedAt": null,
     "firstPlayedAt": "2026-05-22T18:30:00Z",
     "lastPlayedAt": "2026-05-25T08:11:00Z",
     "rating": null,
     "starred": false,
     "hidden": false,

     "events": [
       { "type": "play",  "at": "2026-05-22T18:30:00Z", "positionSeconds": 0 },
       { "type": "pause", "at": "2026-05-22T19:05:12Z", "positionSeconds": 2112 }
     ],

     "updatedAt": "2026-05-25T08:11:00Z"
   }

6.1.  status values

    +=============+==================================================+
    | Value       | Meaning                                          |
    +=============+==================================================+
    | unplayed    | Listener has never started this episode.         |
    +-------------+--------------------------------------------------+
    | in_progress | Listener started but did not finish.             |
    +-------------+--------------------------------------------------+
    | completed   | Listener reached the end or marked complete.     |
    +-------------+--------------------------------------------------+
    | archived    | Listener explicitly dismissed without listening. |
    +-------------+--------------------------------------------------+

                                 Table 2







Trimplayer Editors      Expires 29 November 2026                [Page 8]

Internet-Draft                  PortCast                        May 2026


   positionSeconds is REQUIRED when status is in_progress and SHOULD be
   zero or omitted otherwise.  Producers SHOULD record a small tolerance
   (for example, treating the episode as completed if the listener
   reached within 30 seconds of the end).

6.2.  Playback events

   The events array is OPTIONAL.  Each event has a type from the set
   {play, pause, seek, complete, speed_change, bookmark} and attaches
   its own typed fields.  Producers MAY include a subset; consumers MAY
   ignore events they do not understand.  Producers that do not track
   event-level history can omit events entirely; the top-level fields
   (positionSeconds, playCount, lastPlayedAt) are still sufficient for
   everyday "where did I leave off" portability.

7.  Queue

   {
     "queue": [
       { "position": 1, "episodeRef": { "guid": "https://example.com/ep/42" },
         "addedAt": "2026-05-25T09:00:00Z", "source": "manual" },
       { "position": 2, "episodeRef": { "enclosureUrl": "https://.../ep43.mp3" },
         "addedAt": "2026-05-25T09:01:00Z", "source": "auto" }
     ]
   }

   position is 1-based and MUST be unique within the queue. source is
   free-form (e.g., manual, auto, smart-playlist:Morning Commute).

8.  Bookmarks

   {
     "bookmarkId": "01HXYZ...",
     "episodeRef": { "guid": "https://example.com/ep/42" },
     "atSeconds": 1384.0,
     "endSeconds": 1421.5,
     "label": "Great quote about feed ownership",
     "note": "Quote starts at 'and if you can't take it with you...'",
     "createdAt": "2026-05-25T08:23:00Z",
     "updatedAt": "2026-05-25T08:23:00Z"
   }

   endSeconds is OPTIONAL; its presence promotes a bookmark to a _clip_.

9.  Preferences






Trimplayer Editors      Expires 29 November 2026                [Page 9]

Internet-Draft                  PortCast                        May 2026


   {
     "preferences": {
       "global": {
         "playbackRate": 1.2,
         "skipForwardSeconds": 30,
         "skipBackwardSeconds": 15,
         "trimSilence": true,
         "boostVoice": false
       },
       "perFeed": {
         "917393e3-1b1e-5cef-ace4-edaa54e1f810": {
           "playbackRate": 1.0,
           "skipIntroSeconds": 90,
           "skipOutroSeconds": 60,
           "autoDownload": "latest-3"
         }
       }
     }
   }

   perFeed keys are podcastGuid when available, otherwise feedUrl.  Per-
   feed values override global.

10.  Extensions

   Anything outside this specification lives under an extensions object,
   keyed by reverse-DNS namespace:

   "extensions": {
     "com.trimplayer.skips": [
       { "episodeGuid": "...", "skippedRanges": [[12.0, 47.5]] }
     ],
     "fm.overcast.smart-speed": { "secondsSaved": 18421 }
   }

   Consumers MUST preserve extensions on round-trip even if they do not
   understand a namespace.  This is what keeps a multi-application
   journey lossless.

11.  Versioning

   portcast is a semantic-versioned string.  Consumers:

   *  MUST accept any document whose portcast major version they
      support.

   *  MAY warn the listener when a minor version is newer than they
      understand.



Trimplayer Editors      Expires 29 November 2026               [Page 10]

Internet-Draft                  PortCast                        May 2026


   *  MUST NOT silently drop fields they do not recognise; preserve them
      under extensions._unknown if necessary.

12.  Live sync API (v0.2 -- Draft)

   The file format defined above (v0.1) is the interoperability floor.
   v0.2 introduces an optional API mode: the same entities, exposed over
   HTTPS so clients can synchronise incrementally without a full re-
   export.  The wire payloads in API mode reuse the v0.1 schemas; no new
   entity shapes are introduced.

   A conforming v0.2 implementation MAY implement file mode, API mode,
   or both.  Clients MUST assume nothing beyond what a server advertises
   in its discovery document (Section 12.2).

12.1.  Operating modes

    +======+==============================+===========================+
    | Mode | Transport                    | Use case                  |
    +======+==============================+===========================+
    | File | User-supplied .portcast.json | One-shot migration,       |
    |      |                              | archival, manual transfer |
    +------+------------------------------+---------------------------+
    | API  | HTTPS endpoints on the       | Live sync between two     |
    |      | application's domain         | installed applications    |
    +------+------------------------------+---------------------------+

                                  Table 3

   File mode is the interoperability floor: every API-mode server SHOULD
   implement at least GET /portcast/v1/export, which returns the same
   document a file export would produce.

12.2.  Discovery

   A PortCast server SHOULD publish a discovery document at /.well-
   known/portcast [RFC8615]:














Trimplayer Editors      Expires 29 November 2026               [Page 11]

Internet-Draft                  PortCast                        May 2026


   {
     "portcast": "0.2.0",
     "base": "https://example.app/portcast/v1",
     "auth": {
       "type": "oauth2",
       "authorizationEndpoint": "https://example.app/oauth/authorize",
       "tokenEndpoint": "https://example.app/oauth/token",
       "scopes": ["portcast.read", "portcast.write", "portcast.history"]
     },
     "capabilities": [
       "export",
       "subscriptions.read", "subscriptions.write",
       "episodes.read", "episodes.write",
       "queue.read", "queue.write",
       "bookmarks.read", "bookmarks.write",
       "preferences.read", "preferences.write",
       "events", "deltas", "webhooks"
     ]
   }

   capabilities is a flat string set.  A read-only server omits *.write
   entries.  A server that does not track per-event history omits
   events.  A server that does not implement delta synchronisation omits
   deltas and clients fall back to fetching full collections.

12.3.  Versioning and content type

   Endpoints live under /portcast/v1/.... The v1 is the API major
   version and is independent of the specification version declared
   inside payloads.  Servers MUST set Content-Type: application/
   vnd.portcast+json on responses; clients SHOULD send a matching Accept
   header.  Backwards-compatible additions (new optional fields, new
   capability strings) MUST NOT bump the API major version.

12.4.  Authentication

   Implementations MUST use one of:

   *  OAuth 2.0 [RFC6749], with scopes drawn from portcast.read,
      portcast.write, portcast.history.  The portcast.history scope
      covers event-level playback data (Section 6.2) and is treated as
      more sensitive than basic read.

   *  Bearer token [RFC6750], appropriate for self-hosted or single-user
      deployments.

   Credentials MUST NOT appear in URL query strings.  Servers MUST
   reject requests over plain HTTP.



Trimplayer Editors      Expires 29 November 2026               [Page 12]

Internet-Draft                  PortCast                        May 2026


12.5.  Endpoints

      +========+========================+===========================+
      | Method | Path                   | Body / Returns            |
      +========+========================+===========================+
      | GET    | /portcast/v1/export    | Full PortCast document (= |
      |        |                        | v0.1 file)                |
      +--------+------------------------+---------------------------+
      | POST   | /portcast/v1/import    | Body: full or partial     |
      |        |                        | PortCast document; server |
      |        |                        | upserts each entity       |
      +--------+------------------------+---------------------------+
      | GET    | /portcast/v1/          | { subscriptions,          |
      |        | subscriptions          | deletions?, syncedAt,     |
      |        |                        | nextCursor? }             |
      +--------+------------------------+---------------------------+
      | GET    | /portcast/v1/          | A Subscription            |
      |        | subscriptions/{ref}    |                           |
      +--------+------------------------+---------------------------+
      | PUT    | /portcast/v1/          | Body: Subscription        |
      |        | subscriptions/{ref}    |                           |
      +--------+------------------------+---------------------------+
      | DELETE | /portcast/v1/          | Unsubscribe               |
      |        | subscriptions/{ref}    |                           |
      +--------+------------------------+---------------------------+
      | GET    | /portcast/v1/episodes  | { episodes, deletions?,   |
      |        |                        | syncedAt, nextCursor? }   |
      +--------+------------------------+---------------------------+
      | POST   | /portcast/v1/episodes  | Body: { episodes: [...]   |
      |        |                        | }; upsert                 |
      +--------+------------------------+---------------------------+
      | GET    | /portcast/v1/queue     | { queue }                 |
      +--------+------------------------+---------------------------+
      | PUT    | /portcast/v1/queue     | Body: { queue }; replaces |
      |        |                        | in full                   |
      +--------+------------------------+---------------------------+
      | GET    | /portcast/v1/bookmarks | { bookmarks, deletions?,  |
      |        |                        | syncedAt, nextCursor? }   |
      +--------+------------------------+---------------------------+
      | POST   | /portcast/v1/bookmarks | Body: Bookmark            |
      +--------+------------------------+---------------------------+
      | DELETE | /portcast/v1/          |                           |
      |        | bookmarks/{bookmarkId} |                           |
      +--------+------------------------+---------------------------+
      | GET    | /portcast/v1/          | Preferences               |
      |        | preferences            |                           |
      +--------+------------------------+---------------------------+
      | PUT    | /portcast/v1/          | Body: Preferences         |



Trimplayer Editors      Expires 29 November 2026               [Page 13]

Internet-Draft                  PortCast                        May 2026


      |        | preferences            |                           |
      +--------+------------------------+---------------------------+

                                  Table 4

   {ref} in subscription paths is the URL-encoded podcastGuid when
   known, otherwise the URL-encoded feedUrl.  Servers MUST accept either
   form.  Episodes are intentionally not addressed by path because RSS
   GUIDs do not round-trip cleanly through URL encoding.

12.6.  Delta sync

   Collection endpoints (subscriptions, episodes, bookmarks) MUST accept
   ?since=<RFC 3339 timestamp> when the server advertises the deltas
   capability.  The response then contains only entities whose updatedAt
   > since, a deletions array of refs for entities removed since that
   timestamp, and a syncedAt timestamp the client persists for the next
   round.

   Servers SHOULD retain deletion tombstones for at least 30 days.
   Clients that have been offline longer SHOULD discard their cached
   syncedAt and perform a full pull.

12.7.  Pagination

   Endpoints that may return large collections support cursor-based
   pagination.  The response carries nextCursor when more pages exist;
   the client passes ?cursor=<value> to fetch the next page.  Cursors
   are opaque strings. since and cursor MAY be combined.

12.8.  Conditional updates

   Writes SHOULD use If-Match: <updatedAt> for optimistic concurrency.
   Servers MUST respond 412 Precondition Failed if the resource's
   current updatedAt is newer than the supplied value.  This prevents
   two clients clobbering each other's position updates on the same
   episode.

12.9.  Errors

   Errors are JSON, with HTTP status reflecting the class:










Trimplayer Editors      Expires 29 November 2026               [Page 14]

Internet-Draft                  PortCast                        May 2026


   {
     "error": {
       "code": "subscription_not_found",
       "message": "No subscription matched podcastGuid=917393e3-...",
       "ref": { "podcastGuid": "917393e3-..." }
     }
   }

   The initial set of code values is registered in Section 14.4.
   Servers MAY define additional codes under a reverse-DNS prefix (e.g.,
   com.example.quota_exceeded); such vendor-prefixed codes do not
   require IANA registration.

12.10.  Webhooks (optional)

   Servers advertising the webhooks capability accept registrations at
   POST /portcast/v1/webhooks with body:

   {
     "url": "https://client.example/portcast/hook",
     "events": ["episode.updated", "subscription.added",
                "subscription.removed", "queue.updated"],
     "secret": "<shared secret, >= 32 bytes>"
   }

   Webhook deliveries carry X-PortCast-Signature: sha256=<hex> computed
   over the raw request body with the registration secret as the HMAC
   key.  Receivers MUST verify the signature and SHOULD respond 2xx
   within 5 seconds.  Servers SHOULD retry failed deliveries with
   exponential backoff for at least 24 hours.

   Webhooks are an optimisation; the baseline pattern is client-driven
   polling with ?since=.

12.11.  Capability fallback

   A client that needs a capability the server does not advertise SHOULD
   fall back to GET /portcast/v1/export and process the returned
   document as a file-mode import.  This guarantees a baseline
   interoperability floor even for minimal server implementations.

12.12.  Federation

   PortCast is intentionally federated.  Each application exposes its
   own endpoint on its own domain; there is no central directory or hub.
   A client connecting a new account typically:

   1.  Asks the listener for the application's domain.



Trimplayer Editors      Expires 29 November 2026               [Page 15]

Internet-Draft                  PortCast                        May 2026


   2.  Fetches https://<domain>/.well-known/portcast.

   3.  Runs the OAuth dance against the endpoints declared there.

   4.  Begins delta-synchronising.

   Servers MUST NOT require registration with any central authority to
   be considered conforming.

13.  Security considerations

13.1.  Sensitivity of the data

   A PortCast document is a detailed record of personal listening
   behaviour: which shows a listener follows, when they started or
   finished an episode, which passages they bookmarked, and (when event-
   level history is included) the moment-to-moment shape of their
   attention.  Implementers MUST treat PortCast documents and API
   responses as personal data of comparable sensitivity to browser
   history or messaging metadata.

13.2.  Producer requirements

   Producers:

   *  SHOULD let the listener choose whether to include owner.

   *  SHOULD let the listener choose whether to include event-level
      history (Section 6.2).

   *  SHOULD NOT include device identifiers, IP addresses, geolocation,
      or third-party analytics identifiers in any PortCast field,
      including inside extensions.

   *  MUST NOT embed the listener's account credentials, API keys, OAuth
      tokens, or session cookies anywhere in a PortCast document.

   *  SHOULD warn the listener before transmitting a PortCast document
      to a third party.

13.3.  Consumer requirements

   Consumers SHOULD treat an imported document as personal data, not as
   shareable telemetry.  Consumers MUST NOT retransmit a received
   document to third parties without explicit listener consent.
   Consumers SHOULD make it possible for the listener to delete imported
   data on demand.




Trimplayer Editors      Expires 29 November 2026               [Page 16]

Internet-Draft                  PortCast                        May 2026


13.4.  Transport and storage (API mode)

   In API mode (Section 12):

   *  Servers MUST require TLS; plain HTTP MUST be refused.

   *  Credentials MUST NOT appear in URL query strings or path
      components; they MUST be carried in HTTP request headers.

   *  Servers MUST scope OAuth tokens to a single listener account.

   *  Servers SHOULD support per-client token revocation.

   *  The portcast.history scope SHOULD be requested separately from
      portcast.read.

13.5.  Threat model and out-of-scope risks

   PortCast does not, in v0.1, define a signing or sealing mechanism: a
   document cannot be cryptographically attributed to the producer that
   wrote it.  Consumers SHOULD treat the source of a document as out-of-
   band-authenticated (e.g., the listener manually selected the file or
   authorised the OAuth client).  Adding a signed manifest is listed as
   a future work item.

   PortCast does not protect against a malicious application that has
   been granted access to a listener's data; access control is the
   responsibility of the producing or hosting application, not the
   protocol.  Consumers SHOULD apply input validation to imported
   documents (notably to URL fields and extensions content) consistent
   with their platform's safe-handling guidance.

14.  IANA considerations

   This document requests four IANA actions.

14.1.  Media type registration

   IANA is requested to register the following media type per [RFC6838]:












Trimplayer Editors      Expires 29 November 2026               [Page 17]

Internet-Draft                  PortCast                        May 2026


         +==================+====================================+
         | Field            | Value                              |
         +==================+====================================+
         | Type name        | application                        |
         +------------------+------------------------------------+
         | Subtype name     | vnd.portcast+json                  |
         +------------------+------------------------------------+
         | Required         | none                               |
         | parameters       |                                    |
         +------------------+------------------------------------+
         | Optional         | none                               |
         | parameters       |                                    |
         +------------------+------------------------------------+
         | Encoding         | binary; PortCast documents are     |
         | considerations   | UTF-8 encoded JSON                 |
         +------------------+------------------------------------+
         | Security         | See Section 13 of this document    |
         | considerations   |                                    |
         +------------------+------------------------------------+
         | Interoperability | See Section 11 of this document    |
         | cons.            |                                    |
         +------------------+------------------------------------+
         | Published        | This document                      |
         | specification    |                                    |
         +------------------+------------------------------------+
         | Applications     | Podcast applications, subscription |
         | that use it      | importers and exporters, listener- |
         |                  | data synchronisation services      |
         +------------------+------------------------------------+
         | Fragment         | JSON Pointer syntax [RFC6901]      |
         | identifier       |                                    |
         +------------------+------------------------------------+
         | Restrictions on  | none                               |
         | use              |                                    |
         +------------------+------------------------------------+
         | Provisional      | yes (until RFC publication)        |
         | registration     |                                    |
         +------------------+------------------------------------+
         | Author / change  | The editors of this specification  |
         | controller       |                                    |
         +------------------+------------------------------------+
         | Intended usage   | COMMON                             |
         +------------------+------------------------------------+

                                  Table 5






Trimplayer Editors      Expires 29 November 2026               [Page 18]

Internet-Draft                  PortCast                        May 2026


   The file extension .portcast.json is the RECOMMENDED extension; the
   +json structured-syntax suffix indicates the underlying JSON
   serialization.

14.2.  Well-known URI registration

   IANA is requested to register a new entry in the "Well-Known URIs"
   registry per [RFC8615]:

      +===============+============================================+
      | Field         | Value                                      |
      +===============+============================================+
      | URI suffix    | portcast                                   |
      +---------------+--------------------------------------------+
      | Change        | The editors of this specification          |
      | controller    |                                            |
      +---------------+--------------------------------------------+
      | Specification | This document (Section 12.2)               |
      | document      |                                            |
      +---------------+--------------------------------------------+
      | Related       | The resource is a JSON object describing a |
      | information   | PortCast API endpoint (its base URL,       |
      |               | authentication scheme, and capability set) |
      +---------------+--------------------------------------------+
      | Status        | provisional                                |
      +---------------+--------------------------------------------+

                                 Table 6

14.3.  OAuth 2.0 scope registration

   IANA is requested to register the following OAuth 2.0 scopes per
   [RFC6749] and [RFC8809]:


















Trimplayer Editors      Expires 29 November 2026               [Page 19]

Internet-Draft                  PortCast                        May 2026


      +==================+=========================================+
      | Scope name       | Description                             |
      +==================+=========================================+
      | portcast.read    | Read subscriptions, episode state       |
      |                  | (excluding event history), queue,       |
      |                  | bookmarks, and preferences              |
      +------------------+-----------------------------------------+
      | portcast.write   | Create, update, and delete the same     |
      |                  | entities the portcast.read scope grants |
      |                  | visibility into                         |
      +------------------+-----------------------------------------+
      | portcast.history | Read or write event-level playback      |
      |                  | history (Section 6.2).  MUST be         |
      |                  | requested separately from portcast.read |
      +------------------+-----------------------------------------+

                                 Table 7

   Change controller: the editors of this specification.

14.4.  PortCast error code registry

   This document establishes a new IANA registry titled "PortCast Error
   Codes" with the following structure:

   +=============+=====================================================+
   | Field       | Type / notes                                        |
   +=============+=====================================================+
   | code        | A short, lowercase, underscore-                     |
   |             | separated identifier returned in                    |
   |             | API error responses (Section 12.9)                  |
   +-------------+-----------------------------------------------------+
   | description | A one-sentence summary of when the                  |
   |             | error is returned                                   |
   +-------------+-----------------------------------------------------+
   | reference   | The document defining the code                      |
   +-------------+-----------------------------------------------------+

                                  Table 8

   The registration policy is Specification Required [RFC8126].  Initial
   contents:









Trimplayer Editors      Expires 29 November 2026               [Page 20]

Internet-Draft                  PortCast                        May 2026


   +========================+==============================+===========+
   | Code                   | Description                  | Reference |
   +========================+==============================+===========+
   | unauthorized           | The request lacks valid      | This      |
   |                        | authentication credentials   | document  |
   +------------------------+------------------------------+-----------+
   | forbidden              | The credentials do not       | This      |
   |                        | grant access to the          | document  |
   |                        | resource                     |           |
   +------------------------+------------------------------+-----------+
   | not_found              | The referenced entity does   | This      |
   |                        | not exist                    | document  |
   +------------------------+------------------------------+-----------+
   | conflict               | The request conflicts with   | This      |
   |                        | current server state         | document  |
   +------------------------+------------------------------+-----------+
   | precondition_failed    | An If-Match precondition     | This      |
   |                        | was not satisfied            | document  |
   |                        | (Section 12.8)               |           |
   +------------------------+------------------------------+-----------+
   | invalid_request        | The request body or          | This      |
   |                        | parameters are malformed     | document  |
   +------------------------+------------------------------+-----------+
   | unsupported_capability | The client asked for a       | This      |
   |                        | capability the server does   | document  |
   |                        | not advertise                |           |
   +------------------------+------------------------------+-----------+
   | rate_limited           | The client has exceeded a    | This      |
   |                        | server-defined rate limit    | document  |
   +------------------------+------------------------------+-----------+
   | internal_error         | The server encountered an    | This      |
   |                        | unexpected error             | document  |
   +------------------------+------------------------------+-----------+

                                  Table 9

15.  References

15.1.  Normative References

   [JSON-SCHEMA-2020-12]
              Wright, A., Andrews, H., Hutton, B., and G. Dennis, "JSON
              Schema: A Media Type for Describing JSON Documents (Draft
              2020-12)", n.d.,
              <https://json-schema.org/draft/2020-12/schema>.






Trimplayer Editors      Expires 29 November 2026               [Page 21]

Internet-Draft                  PortCast                        May 2026


   [PODCAST-NAMESPACE]
              Podcasting 2.0 Project, "The 'podcast' Namespace -
              podcast:guid element", n.d.,
              <https://podcastindex.org/namespace/1.0#guid>.

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/rfc/rfc2119>.

   [RFC3339]  Klyne, G. and C. Newman, "Date and Time on the Internet:
              Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002,
              <https://www.rfc-editor.org/rfc/rfc3339>.

   [RFC6749]  Hardt, D., Ed., "The OAuth 2.0 Authorization Framework",
              RFC 6749, DOI 10.17487/RFC6749, October 2012,
              <https://www.rfc-editor.org/rfc/rfc6749>.

   [RFC6750]  Jones, M. and D. Hardt, "The OAuth 2.0 Authorization
              Framework: Bearer Token Usage", RFC 6750,
              DOI 10.17487/RFC6750, October 2012,
              <https://www.rfc-editor.org/rfc/rfc6750>.

   [RFC6838]  Freed, N., Klensin, J., and T. Hansen, "Media Type
              Specifications and Registration Procedures", BCP 13,
              RFC 6838, DOI 10.17487/RFC6838, January 2013,
              <https://www.rfc-editor.org/rfc/rfc6838>.

   [RFC6901]  Bryan, P., Ed., Zyp, K., and M. Nottingham, Ed.,
              "JavaScript Object Notation (JSON) Pointer", RFC 6901,
              DOI 10.17487/RFC6901, April 2013,
              <https://www.rfc-editor.org/rfc/rfc6901>.

   [RFC8126]  Cotton, M., Leiba, B., and T. Narten, "Guidelines for
              Writing an IANA Considerations Section in RFCs", BCP 26,
              RFC 8126, DOI 10.17487/RFC8126, June 2017,
              <https://www.rfc-editor.org/rfc/rfc8126>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/rfc/rfc8174>.

   [RFC8259]  Bray, T., Ed., "The JavaScript Object Notation (JSON) Data
              Interchange Format", STD 90, RFC 8259,
              DOI 10.17487/RFC8259, December 2017,
              <https://www.rfc-editor.org/rfc/rfc8259>.





Trimplayer Editors      Expires 29 November 2026               [Page 22]

Internet-Draft                  PortCast                        May 2026


   [RFC8615]  Nottingham, M., "Well-Known Uniform Resource Identifiers
              (URIs)", RFC 8615, DOI 10.17487/RFC8615, May 2019,
              <https://www.rfc-editor.org/rfc/rfc8615>.

   [RFC8809]  Hodges, J., Mandyam, G., and M. Jones, "Registries for Web
              Authentication (WebAuthn)", RFC 8809,
              DOI 10.17487/RFC8809, August 2020,
              <https://www.rfc-editor.org/rfc/rfc8809>.

15.2.  Informative References

   [OPML2.0]  Winer, D., "OPML 2.0 Specification", October 2007,
              <http://opml.org/spec2.opml>.

   [RFC4846]  Klensin, J., Ed. and D. Thaler, Ed., "Independent
              Submissions to the RFC Editor", RFC 4846,
              DOI 10.17487/RFC4846, July 2007,
              <https://www.rfc-editor.org/rfc/rfc4846>.

   [RFC5378]  Bradner, S., Ed. and J. Contreras, Ed., "Rights
              Contributors Provide to the IETF Trust", BCP 78, RFC 5378,
              DOI 10.17487/RFC5378, November 2008,
              <https://www.rfc-editor.org/rfc/rfc5378>.

   [RFC5744]  Braden, R. and J. Halpern, "Procedures for Rights Handling
              in the RFC Independent Submission Stream", RFC 5744,
              DOI 10.17487/RFC5744, December 2009,
              <https://www.rfc-editor.org/rfc/rfc5744>.

   [RFC7033]  Jones, P., Salgueiro, G., Jones, M., and J. Smarr,
              "WebFinger", RFC 7033, DOI 10.17487/RFC7033, September
              2013, <https://www.rfc-editor.org/rfc/rfc7033>.

Acknowledgments

   PortCast builds on a long tradition of attempts to make a listener's
   relationship with their podcasts portable.  The editors thank Dave
   Winer for OPML, which has carried podcast subscriptions across
   applications for two decades and which inspired the goal of doing the
   same for the rest of a listener's data.  The editors also thank the
   Podcast Namespace project for <podcast:guid>, which makes cross-
   application show identity tractable, and the podcast-application
   development community for feedback on early drafts.

Author's Address

   Trimplayer Editors
   Trimplayer



Trimplayer Editors      Expires 29 November 2026               [Page 23]

Internet-Draft                  PortCast                        May 2026


   Email: trimplayerapp@gmail.com
   URI:   https://trimplayer.com/

















































Trimplayer Editors      Expires 29 November 2026               [Page 24]
