> ## Documentation Index
> Fetch the complete documentation index at: https://docs.punchplay.tv/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Implement PunchPlay auth for browser, native, desktop, and TV apps.

The PunchPlay Platform API supports two app-bound auth patterns:

* Authorization code + PKCE for browser, website, and native apps that can receive a callback
* Device code for TV, desktop, CLI, and clients without a convenient browser callback

Both flows issue scoped bearer tokens for `/api/platform/v1`.

## Register your app first

Create a developer app at [Get API key](https://punchplay.tv/developers).

Each app receives a stable `client_id`.

* Confidential clients also receive a `client_secret`
* Public clients use `client_id` only
* Every app defines its maximum `allowed_scopes`
* The approval screen shows the app name and requested scopes before the user approves access

## Client types

| Client type  | Best for                                         | Credentials                   | Guidance                                 |
| ------------ | ------------------------------------------------ | ----------------------------- | ---------------------------------------- |
| Public       | Browser apps, native apps, desktop apps, TV apps | `client_id`                   | Do not embed a secret you cannot protect |
| Confidential | Server-backed apps and websites                  | `client_id` + `client_secret` | Keep the secret server-side only         |

## Current scopes

Apps can currently request these scopes:

| Scope                 | Allows                                                           |
| --------------------- | ---------------------------------------------------------------- |
| `profile:read`        | Basic profile, title metadata, seasons, episodes, and calendar   |
| `history:read`        | Watch-history reads                                              |
| `history:write`       | Log, edit, and remove watches                                    |
| `lists:read`          | Read the user's lists and list details                           |
| `lists:write`         | Create, edit, delete, and populate lists                         |
| `ratings:read`        | Ratings, favourites, watch statuses, and title interaction state |
| `ratings:write`       | Ratings, favourites, planning, and show-status changes           |
| `collection:read`     | Read the physical and digital collection                         |
| `collection:write`    | Add and remove collection items                                  |
| `notifications:read`  | Read notifications, unread counts, and notification preferences  |
| `notifications:write` | Mark notifications read and update notification preferences      |
| `trophies:read`       | Read trophy progress and earned totals                           |
| `profile:write`       | Update profile details and app preferences                       |
| `playback:read`       | In-progress and Continue Watching state                          |
| `playback:write`      | Playback updates and dismissing progress                         |
| `events:read`         | Playback server-sent events                                      |

If a request omits `scope`, PunchPlay grants the app's configured `allowed_scopes`.
If a request asks for a scope outside the app's `allowed_scopes`, the request fails with `invalid_scope`.

`email:read` is reserved and is not available to developer apps. Use the
stable user ID returned by `GET /api/platform/v1/me` when linking accounts.

PunchPlay's `/me/push-devices` routes are also reserved for developer apps
explicitly marked as first-party. The notification scopes remain available to
third-party apps for inbox, unread-state, and preference features.

## Flow selection

Use this rule:

* Use authorization code + PKCE for browser, website, and native apps that can redirect a user and receive a callback
* Use device code for TV, desktop, CLI, and clients that cannot handle a normal browser callback cleanly

## Authorization code + PKCE

### 1. Redirect the user to PunchPlay

```text theme={null}
GET https://punchplay.tv/api/platform/v1/oauth/authorize
```

Required query parameters:

* `response_type=code`
* `client_id=YOUR_CLIENT_ID`
* `redirect_uri=YOUR_REGISTERED_REDIRECT_URI`
* `code_challenge=BASE64URL_SHA256_OF_CODE_VERIFIER`
* `code_challenge_method=S256`

Optional:

* `scope=profile:read playback:read playback:write events:read`
* `state=YOUR_OPAQUE_STATE`

Example:

```text theme={null}
https://punchplay.tv/api/platform/v1/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fexample.com%2Fauth%2Fpunchplay%2Fcallback&code_challenge=YOUR_PKCE_CHALLENGE&code_challenge_method=S256&scope=profile%3Aread%20playback%3Aread%20playback%3Awrite&state=YOUR_STATE
```

PunchPlay authenticates the user if needed, shows the consent screen, then redirects with HTTP `302` back to your `redirect_uri` with:

* `code`
* `state` if you sent one
* `scope`

If the request cannot be completed, PunchPlay redirects back with an `error`. Redirect-based failures may also include `request_id` so the developer has something concrete to report.

Users can review and revoke approved apps later from `https://punchplay.tv/settings/connected-apps`.

### 2. Exchange the code for tokens

```bash theme={null}
curl -X POST https://punchplay.tv/api/platform/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code=AUTHORIZATION_CODE" \
  -d "redirect_uri=https://example.com/auth/punchplay/callback" \
  -d "code_verifier=YOUR_ORIGINAL_PKCE_VERIFIER"
```

Public apps send `client_id` only. Confidential apps send both `client_id` and `client_secret`.

Successful response:

```json theme={null}
{
  "access_token": "access_token",
  "refresh_token": "refresh_token",
  "token_type": "bearer",
  "expires_in": 2592000,
  "refresh_expires_in": 31536000,
  "scope": "profile:read playback:read playback:write"
}
```

### 3. Refresh the access token

```bash theme={null}
curl -X POST https://punchplay.tv/api/platform/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "refresh_token=REFRESH_TOKEN"
```

Successful refresh returns a rotated refresh token. Replace the old one immediately.

```json theme={null}
{
  "access_token": "new_access_token",
  "refresh_token": "new_refresh_token",
  "token_type": "bearer",
  "expires_in": 2592000,
  "refresh_expires_in": 31415926,
  "scope": "profile:read playback:read playback:write"
}
```

## Device code

### 1. Request a device code

```bash theme={null}
curl -X POST https://punchplay.tv/api/platform/v1/auth/device/code \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "scope": "profile:read playback:read playback:write events:read"
  }'
```

Public clients omit `client_secret`.

Response fields:

| Field                       | Meaning                                              |
| --------------------------- | ---------------------------------------------------- |
| `user_code`                 | Short code shown to the user                         |
| `device_code`               | Opaque code your app uses to poll                    |
| `verification_uri`          | Approval page                                        |
| `verification_uri_complete` | Approval page with code prefilled                    |
| `verification_uri_qr`       | QR code image data                                   |
| `expires_in`                | Device-code lifetime in seconds                      |
| `scope`                     | The scope set that will be attached to issued tokens |

### 2. Poll the token endpoint

```bash theme={null}
curl -X POST https://punchplay.tv/api/platform/v1/auth/device/token \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "device_code": "raw_device_code",
    "device_name": "PunchPlay Companion",
    "device_id": "living-room-mac"
  }'
```

Pending approval returns HTTP `400` with the standard OAuth device-flow error:

```json theme={null}
{ "error": "authorization_pending", "message": "Device code has not been approved yet.", "request_id": "ppreq_..." }
```

Success returns:

```json theme={null}
{
  "access_token": "access_token",
  "refresh_token": "refresh_token",
  "token_type": "bearer",
  "expires_in": 2592000,
  "refresh_expires_in": 31536000,
  "username": "scott",
  "scope": "profile:read playback:read playback:write events:read"
}
```

### 3. Refresh the access token

```bash theme={null}
curl -X POST https://punchplay.tv/api/platform/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "client_id":"YOUR_CLIENT_ID",
    "client_secret":"YOUR_CLIENT_SECRET",
    "refresh_token":"REFRESH_TOKEN"
  }'
```

Successful refresh returns a rotated refresh token. Replace the old one immediately.

```json theme={null}
{
  "access_token": "new_access_token",
  "refresh_token": "new_refresh_token",
  "token_type": "bearer",
  "expires_in": 2592000,
  "refresh_expires_in": 31415926,
  "scope": "profile:read playback:read playback:write events:read"
}
```

## Authenticated requests

Use the access token in the `Authorization` header:

```http theme={null}
Authorization: Bearer ACCESS_TOKEN
```

Platform tokens are scoped to the platform namespace and intended for endpoints under `/api/platform/v1`.

## Debugging auth failures

Platform auth errors include:

* `request_id` in the JSON body
* `X-PunchPlay-Request-Id` in the response headers

PunchPlay also persists platform request logs keyed by `request_id`.

When reporting a failure, capture:

* `request_id`
* UTC timestamp
* endpoint path
* `client_id`
* status code
* raw error body

## Security guidance

* Treat `client_secret`, refresh tokens, and access tokens as sensitive credentials.
* Do not embed `client_secret` in native apps, browser apps, or sample code intended for public distribution.
* Use public clients when your app cannot safely hold a secret.
* Keep `allowed_scopes` as narrow as possible for each app.
* Rotate secrets immediately if you suspect they were exposed.
* Deactivate an app if you need to revoke its issued tokens quickly.
* If your app runs entirely in the browser, keep in mind that standard `EventSource` clients cannot attach custom authorization headers.

## Recommended polling behavior

Poll every few seconds until the user approves the request or the device code expires. Back off if your app has multiple concurrent authorization attempts.

Authentication JSON and form bodies are limited to 64 KiB and must arrive
within 10 seconds. Oversized bodies return `413`; timed-out bodies return `408`.

## Check the authenticated identity

After obtaining a token, verify who you are acting as:

```bash theme={null}
curl https://punchplay.tv/api/platform/v1/me \
  -H "Authorization: Bearer ACCESS_TOKEN"
```
