Skip to content

API Design — Stream, Progress, and Download#

Starting Playback#

When a user clicks Play on a movie, the client sends a single request:

GET /api/v1/stream?movie_id=m_123

Headers:
  Authorization: Bearer <JWT>

Response 200 OK:
{
  "stream_url": "https://...",
  "resume_position_seconds": 0
}

The server does not send video data directly. That would mean holding a gigabyte-scale connection open for every concurrent viewer — at Netflix scale with 200M+ subscribers, the API servers would collapse instantly. Instead, the server returns a URL pointing to where the video lives. The client uses that URL to fetch the actual video separately. How that URL is generated and what infrastructure backs it is a deep dive concern — at the API layer, the contract is just: here is a URL, here is where to start.

resume_position_seconds tells the client where to begin playback. On a first watch, this comes back as 0 — start from the beginning. On a returning watch, it comes back as however far the user got last time. The client seeks directly to that position — that's the "Continue Watching from 30:47" you see on Netflix.


The Full Playback Loop#

Here is the complete flow from first click to resuming on a different device.

sequenceDiagram
    participant C as Client
    participant A as API Server

    Note over C,A: First time watching
    C->>A: GET /api/v1/stream?movie_id=m_123
    A-->>C: { stream_url: "...", resume_position_seconds: 0 }
    Note over C: Playback starts from 0:00

    Note over C,A: While watching — client saves position every ~10s
    C->>A: POST /api/v1/stream/progress { position_seconds: 120 }
    A-->>C: 204 No Content
    C->>A: POST /api/v1/stream/progress { position_seconds: 240 }
    A-->>C: 204 No Content
    C->>A: POST /api/v1/stream/progress { position_seconds: 1847 }
    A-->>C: 204 No Content

    Note over C: User closes Netflix at 30:47

    Note over C,A: Next day — different device
    C->>A: GET /api/v1/stream?movie_id=m_123
    A-->>C: { stream_url: "...", resume_position_seconds: 1847 }
    Note over C: Playback resumes from 30:47

Saving Position — The Progress Endpoint#

While you're watching, the client silently fires a progress update every few seconds in the background. The user never sees this — it just happens.

POST /api/v1/stream/progress

Headers:
  Authorization: Bearer <JWT>

Body:
{
  "movie_id": "m_123",
  "position_seconds": 1847
}

Response 204 No Content

204 — there is nothing meaningful to return. The server saved the position and the client carries on. The reason this is a separate endpoint rather than bundled into the stream response is timing: the client doesn't know when the user will stop watching, so it has to keep reporting position continuously throughout playback.

Why periodic updates and not just on-close?

If the client only saved position when the app closed, a crash or lost connection would lose the user's place entirely. Saving every ~10 seconds means the worst case is losing 10 seconds of progress — acceptable.


Downloading for Offline Viewing#

Netflix lets users download content to watch without internet. The download endpoint looks similar to the stream endpoint — the client is still asking for a URL pointing to the video. The difference is what the client does with it: instead of opening a player, it saves the file to local storage.

POST /api/v1/download

Headers:
  Authorization: Bearer <JWT>

Body:
{
  "movie_id": "m_123"
}

Response 200 OK:
{
  "download_url": "https://...",
  "expires_at": "2026-04-23T10:00:00Z"
}

expires_at is not optional — it is a legal requirement. Netflix licenses content from studios. That license has an expiry window. When expires_at passes, the downloaded file must become unplayable. The client checks this timestamp before allowing offline playback.

sequenceDiagram
    participant C as Client
    participant A as API Server

    C->>A: POST /api/v1/download { movie_id: "m_123" }
    A-->>C: { download_url: "...", expires_at: "2026-04-23T10:00:00Z" }

    Note over C: Client saves file locally

    Note over C: User opens downloaded movie before expiry
    Note over C: Client checks expires_at → valid → playback allowed

    Note over C: User opens downloaded movie after expiry
    Note over C: Client checks expires_at → expired → playback blocked

Download ≠ permanent ownership

The file on the user's device is not theirs forever. Once expires_at passes, the client must block playback. This is enforced client-side using the timestamp returned by the server — there is no second server call at playback time to check validity.


Full API Surface#

Endpoint Method Purpose
/api/v1/home GET Home feed — paginated genre rows
/api/v1/stream GET Start or resume playback — returns stream URL and position
/api/v1/stream/progress POST Save current playback position every ~10 seconds
/api/v1/download POST Get a time-limited download URL for offline viewing