Skip to main content
After you send playback writes, use these endpoints to read the latest materialized state.

Required scopes

  • /api/platform/v1/playback/now-playing requires playback:read
  • /api/platform/v1/playback/in-progress requires playback:read
  • /api/platform/v1/me/continue-watching requires playback:read
  • DELETE /api/platform/v1/playback/in-progress/{id} requires playback:write
  • /api/platform/v1/playback/events requires events:read

Now Playing

Use this when you need the single most relevant active playback item for the user. Common response fields:
  • type
  • tmdbId
  • title
  • progressPercent
  • progressSeconds
  • durationSeconds
  • nowPlaying
  • playbackState
  • mediaSource

In Progress

Use this when you need a queue of active or resumable items rather than a single now-playing object. What the queue holds:
  • At most one episode per show: the most recently updated one that is still resumable. Shows the user has dropped are left out
  • An item stays until the user logs a watch of that movie or episode at or after its last playback event. Watches from before it, such as an old import, do not hide a rewatch
  • sourceSeason and sourceEpisode give an episode in TMDB numbering. They differ from season and episode only for an anime episode stored in TVDB order. See Anime episode numbering
For every progress row with no filtering, page through the playback resource of /api/platform/v1/me/sync/snapshot. Dismiss an item with:

Continue Watching

/me/continue-watching returns show-level summaries: the show’s current season/episode and completion state. It is useful for a “next episode” row, but it is not the source of an exact movie or episode resume position. Fetch /api/platform/v1/me/continue-watching/{showId} when you need the details for a show. Use /api/platform/v1/playback/in-progress for resumable movie or episode items with progressSeconds, durationSeconds, and the progress identifier used by the dismiss endpoint. The status field describes the series’ lifecycle, not its current release schedule. For example, Returning Series means the series may return even after its latest season has finished airing. Do not label it “Airing” based on that status alone. Use releasePhase for a schedule-aware badge:
  • airing: a season has released episodes and a dated future episode in that same season
  • returning: a subsequent season is announced, with or without an air date
  • upcoming: an announced first season has not premiered
  • season_complete: released episodes exist but no later episode or season is currently announced
  • schedule_pending: a same-season episode is announced without a future air date, a partly released season has no scheduled next date, or a provider-returning series lacks enough schedule data
  • ended, canceled, and unknown: terminal provider states or insufficient schedule data
This field is best-effort metadata, not a guarantee that a series has been renewed or definitively finished. The status response field and status=returning|airing|ended query filters keep their provider-lifecycle semantics. Legacy airing matches Returning Series; legacy ended matches every other provider status, not just ended shows.

Episode pointers and gaps

nextSeason and nextEpisode identify the first currently available unwatched episode after lastWatchedSeason and lastWatchedEpisode. They never rewind to an earlier gap and never point at an unaired episode. Both values are null when there is nothing playable after the last-watched episode. Earlier gaps are reported separately:
  • earlierUnwatchedSeason and earlierUnwatchedEpisode identify the gap to fill first, or are null when there is no gap. That is the earliest aired unwatched episode in a season the user has started; only when every started season is complete up to the last-watched episode does it fall back to the earliest aired unwatched episode overall
  • earlierUnwatchedCount is the number of aired unwatched episodes before the last-watched episode, including seasons the user has not started
  • earlierUnwatchedSourceSeason and earlierUnwatchedSourceEpisode give the provider coordinates of that gap for building episode links, like nextSourceSeason and nextSourceEpisode; earlierUnwatchedEpisodeTitle is its title when known
A user who starts a show at S03E02 without logging seasons 1–2 has a real gap at S03E01 and two untouched seasons. The pointer names S03E01, and the count includes every episode of seasons 1–2 as well. nextEpisodeToAir is a separate announcement pointer. Its airDate is optional because an episode can be announced before it is scheduled. A caught-up show remains in Continue Watching while this pointer is present, but clients must not treat it as playable until it becomes the nextSeason and nextEpisode. For example, if only S01E06 is watched, episodes 1–5 are available, and S01E07 is announced but has not aired, the summary contains:
After S01E07 airs, nextSeason and nextEpisode advance to 1 and 7; the earlier-gap fields continue to identify episodes 1–5.

Current season

The currentSeason* fields describe the season progress is focused on: nextSeason when an episode is playable, otherwise lastWatchedSeason. currentSeasonEpisodesTotal counts aired episodes only, and currentSeasonMinutesRemaining adds up the listed runtimes of that season’s unwatched aired episodes. episodeRuntimeSource says where episodeRuntimeMinutes came from: history (the average of runtimes in the user’s watch history), episode (the next episode’s listed runtime), show (the provider’s series runtime), or unknown.

Server-sent events

The stream sends lightweight notifications whenever playback state changes. Event types:
  • ready
  • playback-update
playback-update includes:
Use SSE as an invalidation signal, not as the full source of truth.
  1. Open the event stream.
  2. Wait for playback-update.
  3. Fetch /playback/now-playing or /playback/in-progress.
  4. Update your UI from those read endpoints.
An SSE connection is authorized when it is opened. Access tokens expire after one hour and revocation can make an existing credential unusable, so track the token expiry, close the stream before it expires, refresh the token, and reconnect. If a connection drops or a refresh/revocation race produces a 401, refresh or re-authorize before reconnecting. Do not treat an open stream as a permanent authorization grant.

Browser caveat

Standard browser EventSource does not let you attach an Authorization header. If your app runs in the browser, use one of these approaches:
  • Proxy the stream through your backend
  • Use a fetch-based streaming client instead of EventSource
  • Open the SSE stream from a native, desktop, or server environment that supports bearer headers

Cache behavior

Playback read endpoints return live user state and should be treated as non-cacheable application data.

Failure handling

If the SSE connection drops or its handshake returns 503 service_unavailable, wait for the Retry-After delay when one is present (otherwise use bounded exponential backoff) before reconnecting:
  • fetch /playback/now-playing after reconnecting
  • do not assume missed SSE messages contain the full state you need
SSE notifications are lightweight invalidations, not a durable delivery queue. Use the change feed in Partner Sync when a mirror must recover every change, and reconcile with a read endpoint after reconnecting. If your deployment has multiple application processes, verify the event fan-out topology; a process-local listener cannot by itself guarantee cross-process delivery. If the stream handshake fails, capture the X-PunchPlay-Request-Id response header when available and log the endpoint, timestamp, and status code.