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 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 value (preferred). * enclosureUrl -- the media URL from . 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= 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= to fetch the next page. Cursors are opaque strings. since and cursor MAY be combined. 12.8. Conditional updates Writes SHOULD use If-Match: 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": "= 32 bytes>" } Webhook deliveries carry X-PortCast-Signature: sha256= 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:///.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., . 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., . [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, . [RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002, . [RFC6749] Hardt, D., Ed., "The OAuth 2.0 Authorization Framework", RFC 6749, DOI 10.17487/RFC6749, October 2012, . [RFC6750] Jones, M. and D. Hardt, "The OAuth 2.0 Authorization Framework: Bearer Token Usage", RFC 6750, DOI 10.17487/RFC6750, October 2012, . [RFC6838] Freed, N., Klensin, J., and T. Hansen, "Media Type Specifications and Registration Procedures", BCP 13, RFC 6838, DOI 10.17487/RFC6838, January 2013, . [RFC6901] Bryan, P., Ed., Zyp, K., and M. Nottingham, Ed., "JavaScript Object Notation (JSON) Pointer", RFC 6901, DOI 10.17487/RFC6901, April 2013, . [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, . [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, . [RFC8259] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, December 2017, . 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, . [RFC8809] Hodges, J., Mandyam, G., and M. Jones, "Registries for Web Authentication (WebAuthn)", RFC 8809, DOI 10.17487/RFC8809, August 2020, . 15.2. Informative References [OPML2.0] Winer, D., "OPML 2.0 Specification", October 2007, . [RFC4846] Klensin, J., Ed. and D. Thaler, Ed., "Independent Submissions to the RFC Editor", RFC 4846, DOI 10.17487/RFC4846, July 2007, . [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, . [RFC5744] Braden, R. and J. Halpern, "Procedures for Rights Handling in the RFC Independent Submission Stream", RFC 5744, DOI 10.17487/RFC5744, December 2009, . [RFC7033] Jones, P., Salgueiro, G., Jones, M., and J. Smarr, "WebFinger", RFC 7033, DOI 10.17487/RFC7033, September 2013, . 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 , 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]