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

# Streaming API

> Mux-powered live video and audio streaming, session management, video generation, and webhook endpoints

# Streaming API

Endpoints for live video and audio streaming via Mux, DJ session management, video generation, and streaming webhook processing.

### Session lifecycle

A DJ session transitions through several statuses during its lifecycle. The `active` and `live` statuses are both treated as **current sessions** — meaning the session is considered in-progress and occupies a streaming slot for the wallet. Endpoints that check for an existing session, end sessions, or authorize session access treat both statuses uniformly.

| Status       | Meaning                                                                             |
| ------------ | ----------------------------------------------------------------------------------- |
| `active`     | Session created, waiting for or receiving an ingest connection                      |
| `live`       | Mux stream is active and playback is available to listeners                         |
| `ended`      | Session stopped by the DJ (replay assets deleted)                                   |
| `archived`   | Session stopped by the DJ with replay retention (assets preserved, credits charged) |
| `auto-ended` | Session expired (exceeded the 2-hour maximum) or cleaned up automatically           |

## List live streams

```http theme={"dark"}
GET /api/basefm/live
```

Returns all currently active Mux live streams. No authentication required.

The `availability` field reflects the station state:

* `live` — Mux is healthy and at least one DJ is currently broadcasting.
* `idle` — Mux is healthy but no DJs are live (for example, after the most recent set ended).
* `degraded` — Mux credentials are missing or the Mux API returned an error. The response falls back to cached session data from the database.

When Mux is available, active streams are enriched with metadata from the corresponding DJ session record (matched by stream ID). This means fields like `name`, `wallet`, `playbackId`, and `startedAt` can resolve from session data even when Mux stream metadata is sparse or missing.

### Response

```json theme={"dark"}
{
  "djs": [
    {
      "id": "aB1cD2eF3g",
      "name": "DJ Rave",
      "wallet": "0x1234...abcd",
      "playbackId": "xYz789",
      "streamKey": "sk-ab12-cd34-ef56",
      "status": "active",
      "startedAt": "2026-04-11T00:00:00.000Z",
      "source": "mux",
      "hlsUrl": "https://stream.mux.com/xYz789.m3u8",
      "embedUrl": "https://stream.mux.com/xYz789.html"
    }
  ],
  "count": 1,
  "primaryDj": {
    "id": "aB1cD2eF3g",
    "name": "DJ Rave",
    "wallet": "0x1234...abcd",
    "playbackId": "xYz789",
    "streamKey": "sk-ab12-cd34-ef56",
    "status": "active",
    "startedAt": "2026-04-11T00:00:00.000Z",
    "source": "mux",
    "hlsUrl": "https://stream.mux.com/xYz789.m3u8",
    "embedUrl": "https://stream.mux.com/xYz789.html"
  },
  "availability": "live",
  "distribution": {
    "origin": {
      "status": "active",
      "playbackId": "xYz789",
      "hlsUrl": "https://stream.mux.com/xYz789.m3u8"
    },
    "firstParty": {
      "status": "healthy",
      "pageUrl": "https://basefm.space",
      "note": null
    },
    "relays": [],
    "requiredRelayStatus": "healthy",
    "overallStatus": "healthy"
  }
}
```

| Field                              | Type             | Description                                                                                                                                                                                                                                                                                                   |
| ---------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `djs[].id`                         | string           | Mux live stream ID                                                                                                                                                                                                                                                                                            |
| `djs[].name`                       | string           | DJ display name. Resolved from Mux stream metadata first, then from the DJ session record, and defaults to `Anonymous DJ` if neither is available.                                                                                                                                                            |
| `djs[].wallet`                     | string \| null   | DJ wallet address. Resolved from Mux stream metadata first, then from the DJ session record.                                                                                                                                                                                                                  |
| `djs[].playbackId`                 | string \| null   | Mux playback ID. Resolved from the Mux stream first, then from the DJ session record.                                                                                                                                                                                                                         |
| `djs[].streamKey`                  | string \| null   | RTMP stream key. `null` when the DJ is served from the session cache.                                                                                                                                                                                                                                         |
| `djs[].status`                     | string           | Stream status (`active`)                                                                                                                                                                                                                                                                                      |
| `djs[].startedAt`                  | number \| string | ISO 8601 string when a matching DJ session record exists, otherwise a Unix timestamp from the Mux stream. Session-cache entries always return an ISO 8601 string.                                                                                                                                             |
| `djs[].source`                     | string           | Where the DJ data was resolved from: `mux` (live Mux API) or `session-cache` (database fallback)                                                                                                                                                                                                              |
| `djs[].hlsUrl`                     | string \| null   | HLS playlist URL for playback                                                                                                                                                                                                                                                                                 |
| `djs[].embedUrl`                   | string \| null   | Embeddable player URL                                                                                                                                                                                                                                                                                         |
| `count`                            | number           | Number of live DJs                                                                                                                                                                                                                                                                                            |
| `primaryDj`                        | object \| null   | The first DJ in the list, or `null` when no DJs are live                                                                                                                                                                                                                                                      |
| `availability`                     | string           | Stream availability and data source status. One of `live` (at least one DJ is broadcasting, served from Mux), `idle` (Mux is healthy but no DJ is currently live — for example, after a set ended), or `degraded` (Mux API or credentials are unavailable and the response is served from the session cache). |
| `distribution`                     | object           | Distribution state for the baseFM station. See [distribution](#get-distribution-state) for the full shape.                                                                                                                                                                                                    |
| `distribution.origin.status`       | string           | Origin stream status. `active` (a DJ is broadcasting), `idle` (Mux is healthy but no DJ is live — the normal off-air state), or `degraded` (Mux API or credentials are unavailable).                                                                                                                          |
| `distribution.origin.playbackId`   | string \| null   | Mux playback ID for the origin stream                                                                                                                                                                                                                                                                         |
| `distribution.origin.hlsUrl`       | string \| null   | HLS URL for the origin stream                                                                                                                                                                                                                                                                                 |
| `distribution.firstParty`          | object           | Health status of the first-party baseFM page                                                                                                                                                                                                                                                                  |
| `distribution.firstParty.status`   | string           | One of `pending`, `healthy`, `degraded`, `failed`, or `stopped`                                                                                                                                                                                                                                               |
| `distribution.relays`              | array            | Configured relay destinations and their health                                                                                                                                                                                                                                                                |
| `distribution.requiredRelayStatus` | string           | Aggregated status of all required relays                                                                                                                                                                                                                                                                      |
| `distribution.overallStatus`       | string           | Overall distribution health across origin, first-party page, and relays                                                                                                                                                                                                                                       |

When the response has `"availability": "degraded"`, it may also include an `error` field with a human-readable description of why the Mux API could not be reached. An `"availability": "idle"` response is the normal off-air state and does not include an `error` field.

### Errors

| Code | Description                                         |
| ---- | --------------------------------------------------- |
| 500  | Internal error and no cached sessions are available |

<Note>When Mux credentials are missing or the Mux API is unreachable, the endpoint returns cached live sessions from the database with `"availability": "degraded"` and HTTP status `200` (as long as cached data exists). A `500` is only returned when both the Mux API and the session cache are unavailable.</Note>

## Create live stream

```http theme={"dark"}
POST /api/basefm/streams
```

Creates a new Mux live stream. Access is granted if the caller meets any of the following conditions:

* The wallet holds at least **2,500,000 BASEFM** tokens on the Base network, or
* The caller has an active **community guest pass** (available to Builder and Whale tier claimed holders via the community program), or
* The caller pays a **\$5 USDC** session fee on Base by including a verifying `txHash` in the request

<Note>Mux live stream creation only arms the stream — it is not actually live until OBS or ffmpeg connects to the RTMP ingest. Use the [Stream status](#stream-status) endpoint to confirm ingest is up.</Note>

<Note>The `wallet` address used for BASEFM token verification must be a Coinbase Smart Wallet on Base. The platform enforces `smartWalletOnly` as its connector policy — injected wallets (such as MetaMask) are not supported for DJ streaming.</Note>

### Request body

| Field    | Type   | Required    | Description                                                                                                                                                                                                                                         |
| -------- | ------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `wallet` | string | Yes         | Wallet address of the DJ. Used for BASEFM token balance verification.                                                                                                                                                                               |
| `name`   | string | No          | DJ display name (default: `DJ`)                                                                                                                                                                                                                     |
| `city`   | string | No          | City or location label for the DJ. Stored in session metadata and displayed on the dashboard. Trimmed and capped at 80 characters.                                                                                                                  |
| `txHash` | string | Conditional | Transaction hash for the \$5 USDC paid session. Required when the caller does not hold the BASEFM token threshold and does not have a community guest pass. The transfer must be to the platform wallet for the full fee from the request `wallet`. |

### Response (200) — new stream

```json theme={"dark"}
{
  "success": true,
  "stream": {
    "id": "aB1cD2eF3g",
    "name": "DJ Rave",
    "wallet": "0x1234...abcd",
    "streamKey": "sk-ab12-cd34-ef56",
    "rtmpUrl": "rtmp://global-live.mux.com:5222/app",
    "fullRtmpUrl": "rtmp://global-live.mux.com:5222/app/sk-ab12-cd34-ef56",
    "playbackId": "xYz789",
    "status": "created"
  },
  "session": {
    "id": 42,
    "wallet": "0x1234...abcd",
    "maxDuration": 7200,
    "remaining": 7200,
    "expiresAt": "2026-04-11T02:00:00.000Z",
    "accessToken": "eyJhbGciOiJIUzI1NiIs..."
  },
  "ffmpeg": {
    "command": "ffmpeg -re -i \"/path/to/set.mp3\" -c:a aac -b:a 128k -ar 44100 -ac 2 -f flv \"rtmp://global-live.mux.com:5222/app/sk-ab12-cd34-ef56\"",
    "audioOnlyCommand": "ffmpeg -re -i \"/path/to/set.mp3\" -c:a aac -b:a 128k -ar 44100 -ac 2 -f flv \"rtmp://global-live.mux.com:5222/app/sk-ab12-cd34-ef56\"",
    "playlistCommand": "ffmpeg -re -f concat -safe 0 -i \"/tmp/basefm-playlist.txt\" -c:a aac -b:a 128k -ar 44100 -ac 2 -f flv \"rtmp://global-live.mux.com:5222/app/sk-ab12-cd34-ef56\"",
    "artworkCommand": "ffmpeg -re -loop 1 -i \"https://.../basefm-artwork.jpg\" -f lavfi -i anullsrc=... -c:v libx264 -preset veryfast -tune stillimage -pix_fmt yuv420p -vf \"scale=1280:720:...\" -g 60 -r 30 -b:v 3500k -maxrate 4500k -bufsize 7000k -c:a aac -b:a 256k -ar 44100 -ac 2 -f flv \"rtmp://global-live.mux.com:5222/app/sk-ab12-cd34-ef56\"",
    "inputHint": "Default command is audio-only. Use playlistCommand for an ffmpeg concat playlist, or artworkCommand when the DJ explicitly wants a video track with the baseFM standby image."
  }
}
```

### Response (200) — recovery (existing session)

When the authenticated caller already owns a non-expired current session, the endpoint returns the existing stream control payload instead of creating a duplicate stream. This lets DJs recover after a refresh without losing their controls or accidentally creating a new stream.

```json theme={"dark"}
{
  "success": true,
  "reconnected": true,
  "stream": {
    "id": "aB1cD2eF3g",
    "name": "DJ Rave",
    "wallet": "0x1234...abcd",
    "streamKey": "sk-ab12-cd34-ef56",
    "rtmpUrl": "rtmp://global-live.mux.com:5222/app",
    "fullRtmpUrl": "rtmp://global-live.mux.com:5222/app/sk-ab12-cd34-ef56",
    "playbackId": "xYz789",
    "status": "active"
  },
  "ffmpeg": { "command": "...", "audioOnlyCommand": "...", "playlistCommand": "...", "artworkCommand": "...", "inputHint": "..." },
  "session": {
    "id": 42,
    "wallet": "0x1234...abcd",
    "maxDuration": 7200,
    "remaining": 5400,
    "expiresAt": "2026-04-11T02:00:00.000Z",
    "accessToken": "eyJhbGciOiJIUzI1NiIs..."
  }
}
```

| Field                     | Type           | Description                                                                                                                                                                                                      |
| ------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `success`                 | boolean        | Always `true`                                                                                                                                                                                                    |
| `reconnected`             | boolean        | Present and `true` only on a recovery response, when the authenticated caller already owns a non-expired current session and is being handed the existing stream controls.                                       |
| `stream.id`               | string         | Mux live stream ID                                                                                                                                                                                               |
| `stream.name`             | string         | DJ display name                                                                                                                                                                                                  |
| `stream.wallet`           | string         | Wallet for the session                                                                                                                                                                                           |
| `stream.streamKey`        | string         | RTMP stream key                                                                                                                                                                                                  |
| `stream.rtmpUrl`          | string         | RTMP ingest server URL                                                                                                                                                                                           |
| `stream.fullRtmpUrl`      | string         | Full RTMP URL including stream key (for OBS or ffmpeg)                                                                                                                                                           |
| `stream.playbackId`       | string \| null | Mux playback ID for viewers                                                                                                                                                                                      |
| `stream.status`           | string         | Mux or session status (e.g. `created`, `active`)                                                                                                                                                                 |
| `session.id`              | number         | DJ session record ID                                                                                                                                                                                             |
| `session.wallet`          | string         | The wallet address used for the session. When access is granted via a community guest pass, this is the claimed Agentbot wallet (not the wallet sent in the request).                                            |
| `session.maxDuration`     | number         | Maximum session duration in seconds (7200 = 2 hours)                                                                                                                                                             |
| `session.remaining`       | number         | Remaining seconds in the session                                                                                                                                                                                 |
| `session.expiresAt`       | string         | ISO 8601 timestamp when the session expires                                                                                                                                                                      |
| `session.accessToken`     | string         | A signed session token that can be used to authenticate subsequent `GET` and `DELETE` requests on this session without requiring a full user session. Valid for the session duration plus a 1-hour grace period. |
| `ffmpeg.command`          | string         | Default ffmpeg command (audio-only). Suitable for unattended agent streaming with no video track.                                                                                                                |
| `ffmpeg.audioOnlyCommand` | string         | Audio-only ffmpeg command. Encodes a single audio file to the Mux RTMP endpoint.                                                                                                                                 |
| `ffmpeg.playlistCommand`  | string         | ffmpeg concat playlist command. Use for DJs who provide an ffmpeg `concat` playlist file.                                                                                                                        |
| `ffmpeg.artworkCommand`   | string         | Artwork-and-video ffmpeg command. Use when the DJ explicitly wants a video track with the default baseFM standby image.                                                                                          |
| `ffmpeg.inputHint`        | string         | Human-readable hint explaining when to use each command.                                                                                                                                                         |

<Note>The deprecated `stream.accessGrantedBy`, `obsSettings`, `streamType`, and `playback` response fields have been removed. Use the [Stream status](#stream-status) endpoint to read playback URLs and Mux state during a session.</Note>

### Errors

| Code | Description                                                                                                                                                                                                                               |
| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | Wallet address required, or USDC payment verification failed (the response `error` includes the verification reason)                                                                                                                      |
| 402  | Payment required. The caller does not hold the BASEFM token threshold and has no community guest pass, and no `txHash` was provided. The response includes `fee` (USDC) and a human-readable `message`.                                   |
| 403  | Community guest pass only works with the claimed Agentbot wallet. Returned when the caller has a community pass but the `wallet` in the request does not match their claimed wallet address. The response includes the expected `wallet`. |
| 409  | `active_session_exists` — a non-expired current session already exists for this wallet and the authenticated caller does not own it. Owners receive the recovery response above instead.                                                  |
| 429  | `cooldown_active` — the wallet ended a previous session less than 24 hours ago and is in the cooldown window. Admin callers (authenticated with an email on the platform admin allowlist) bypass this check.                              |
| 500  | Mux is not configured or stream creation failed                                                                                                                                                                                           |

<Note>When access is granted via a community guest pass (not BASEFM tokens), the stream is created using the caller's claimed Agentbot wallet address regardless of the `wallet` value in the request. The `stream.wallet` and `session.wallet` fields in the response reflect the claimed wallet.</Note>

<Note>Before checking for a blocking session, the endpoint automatically ends any expired current sessions for the wallet. This means that if a previous session exceeded the 2-hour maximum but was never explicitly stopped, it is cleaned up and the caller can start a new stream without manual intervention.</Note>

<Note>**Admin cooldown bypass:** When the request is made by an authenticated user whose email is on the platform admin allowlist, the 24-hour post-session cooldown is skipped. This lets admins run same-day stream tests. The bypass is tied to the authenticated session — it cannot be triggered from the request body or query parameters. All other access checks (BASEFM token balance, community guest pass, or USDC payment) still apply, and normal DJs remain rate-limited by the cooldown.</Note>

## Check session status

```http theme={"dark"}
GET /api/basefm/streams
```

Returns the active DJ session for the authenticated caller. Authentication is resolved from either a session token or a user session (see [authentication](#authentication) below).

### Authentication

The endpoint accepts two forms of authentication, checked in this order:

1. **Session token** — pass the `accessToken` returned by `POST /api/basefm/streams` as a `sessionToken` query parameter or an `x-basefm-session` header.
2. **User session** — a standard authenticated session (cookie-based). The endpoint looks up the most recent active session for the authenticated user.

### Query parameters

| Parameter      | Type   | Required | Description                                                                                         |
| -------------- | ------ | -------- | --------------------------------------------------------------------------------------------------- |
| `sessionToken` | string | No       | Session access token from `POST /api/basefm/streams`. Alternative to the `x-basefm-session` header. |

### Headers

| Header             | Required | Description                                                              |
| ------------------ | -------- | ------------------------------------------------------------------------ |
| `x-basefm-session` | No       | Session access token. Alternative to the `sessionToken` query parameter. |

### Response — active session

```json theme={"dark"}
{
  "active": true,
  "stream": {
    "id": "aB1cD2eF3g",
    "name": "DJ Rave",
    "wallet": "0x1234...abcd",
    "streamKey": "sk-ab12-cd34-ef56",
    "rtmpUrl": "rtmp://global-live.mux.com:5222/app",
    "fullRtmpUrl": "rtmp://global-live.mux.com:5222/app/sk-ab12-cd34-ef56",
    "playbackId": "xYz789",
    "status": "active",
    "accessGrantedBy": "basefm",
    "ffmpeg": { "command": "...", "audioOnlyCommand": "...", "playlistCommand": "...", "artworkCommand": "...", "inputHint": "..." }
  },
  "ffmpeg": { "command": "...", "audioOnlyCommand": "...", "playlistCommand": "...", "artworkCommand": "...", "inputHint": "..." },
  "session": {
    "id": 42,
    "wallet": "0x1234...abcd",
    "djName": "DJ Rave",
    "muxStreamId": "aB1cD2eF3g",
    "playbackId": "xYz789",
    "startedAt": "2026-04-11T00:00:00.000Z",
    "elapsed": 1800,
    "remaining": 5400,
    "remainingMinutes": 90,
    "expiresAt": "2026-04-11T02:00:00.000Z"
  }
}
```

### Response — no active session

```json theme={"dark"}
{
  "active": false,
  "message": "No active session."
}
```

If the session has expired (exceeded the 2-hour maximum), it is automatically ended and the response returns `active: false` with a `"Session expired."` message.

| Field                      | Type           | Description                                                                                                                                                                                                  |
| -------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `active`                   | boolean        | Whether the caller has an active session                                                                                                                                                                     |
| `stream`                   | object         | The full stream control payload for the active session, mirroring the [Create live stream](#create-live-stream) response. Use this to recover stream controls after a refresh without creating a new stream. |
| `stream.id`                | string         | Mux live stream ID                                                                                                                                                                                           |
| `stream.name`              | string         | DJ display name                                                                                                                                                                                              |
| `stream.wallet`            | string         | Wallet for the session                                                                                                                                                                                       |
| `stream.streamKey`         | string \| null | RTMP stream key (resolved from Mux when reachable)                                                                                                                                                           |
| `stream.rtmpUrl`           | string         | RTMP ingest server URL                                                                                                                                                                                       |
| `stream.fullRtmpUrl`       | string \| null | Full RTMP URL including stream key, when the stream key is available                                                                                                                                         |
| `stream.playbackId`        | string \| null | Mux playback ID                                                                                                                                                                                              |
| `stream.status`            | string         | Mux or session status (e.g. `active`, `idle`)                                                                                                                                                                |
| `stream.accessGrantedBy`   | string \| null | How access was granted: `basefm` (token balance), `community-pass` (Builder/Whale community program perk), or `paid` (USDC session fee)                                                                      |
| `stream.ffmpeg`            | object         | The ffmpeg command templates for the session. See [Create live stream](#create-live-stream) for field shape.                                                                                                 |
| `ffmpeg`                   | object         | Convenience copy of `stream.ffmpeg` at the top level.                                                                                                                                                        |
| `session.id`               | number         | Session record ID                                                                                                                                                                                            |
| `session.wallet`           | string         | Wallet address for the session                                                                                                                                                                               |
| `session.djName`           | string \| null | DJ display name                                                                                                                                                                                              |
| `session.muxStreamId`      | string         | Mux live stream ID stored on the session                                                                                                                                                                     |
| `session.playbackId`       | string \| null | Mux playback ID                                                                                                                                                                                              |
| `session.startedAt`        | string         | ISO 8601 timestamp when the session started                                                                                                                                                                  |
| `session.elapsed`          | number         | Seconds elapsed since the session started                                                                                                                                                                    |
| `session.remaining`        | number         | Seconds remaining before the session expires                                                                                                                                                                 |
| `session.remainingMinutes` | number         | Minutes remaining (rounded down)                                                                                                                                                                             |
| `session.expiresAt`        | string         | ISO 8601 timestamp when the session expires                                                                                                                                                                  |

### Errors

| Code | Description                                                    |
| ---- | -------------------------------------------------------------- |
| 401  | Unauthorized — no valid session token or user session          |
| 403  | Forbidden — the session token does not match the session owner |

<Warning>The `wallet` query parameter is no longer used. Session lookup is now based on the authenticated caller (session token or user session). Requests using the old `?wallet=` parameter are ignored — you must provide authentication instead.</Warning>

## End session

```http theme={"dark"}
DELETE /api/basefm/streams
```

Ends the authenticated caller's current DJ session and retires the corresponding Mux live stream. Replay assets are deleted by default to avoid ongoing storage costs.

Uses the same [authentication](#authentication) as the check session endpoint (session token or user session).

### Query parameters

| Parameter      | Type   | Required | Description                                                                                         |
| -------------- | ------ | -------- | --------------------------------------------------------------------------------------------------- |
| `sessionToken` | string | No       | Session access token from `POST /api/basefm/streams`. Alternative to the `x-basefm-session` header. |

### Headers

| Header             | Required | Description                                                              |
| ------------------ | -------- | ------------------------------------------------------------------------ |
| `x-basefm-session` | No       | Session access token. Alternative to the `sessionToken` query parameter. |

### Response

```json theme={"dark"}
{
  "success": true,
  "message": "Session ended"
}
```

If no active session exists, the endpoint still returns `success: true` with `"message": "No session"`.

| Field     | Type    | Description                                           |
| --------- | ------- | ----------------------------------------------------- |
| `success` | boolean | Always `true`                                         |
| `message` | string  | Human-readable status message describing the outcome. |

<Warning>**Deprecated:** The `archive` request body field and the `ended`, `muxStopped`, `archived`, `archiveCreditCost`, `deletedAssetIds`, and `retainedAssetIds` response fields are no longer returned. The DJ archive flow is currently unavailable through this endpoint — replay assets are always cleaned up when a session ends.</Warning>

### Errors

| Code | Description                                                    |
| ---- | -------------------------------------------------------------- |
| 401  | Unauthorized — no valid session token or user session          |
| 403  | Forbidden — the session token does not match the session owner |

<Warning>The `wallet` query parameter is no longer used. Session lookup is now based on the authenticated caller (session token or user session). Requests using the old `?wallet=` parameter are ignored — you must provide authentication instead.</Warning>

## Stream status

```http theme={"dark"}
GET /api/basefm/streams/status
```

Returns detailed stream health information for the authenticated caller's active session, including the real-time Mux stream status, distribution state, and a health assessment. Uses the same [authentication](#authentication) as the check session and end session endpoints.

### Query parameters

| Parameter      | Type   | Required | Description                                                                                         |
| -------------- | ------ | -------- | --------------------------------------------------------------------------------------------------- |
| `sessionToken` | string | No       | Session access token from `POST /api/basefm/streams`. Alternative to the `x-basefm-session` header. |

### Headers

| Header             | Required | Description                                                              |
| ------------------ | -------- | ------------------------------------------------------------------------ |
| `x-basefm-session` | No       | Session access token. Alternative to the `sessionToken` query parameter. |

### Response — active session

```json theme={"dark"}
{
  "active": true,
  "session": {
    "id": 42,
    "djName": "DJ Rave",
    "muxStreamId": "aB1cD2eF3g",
    "playbackId": "xYz789",
    "status": "live"
  },
  "mux": {
    "id": "aB1cD2eF3g",
    "status": "active",
    "playbackId": "xYz789",
    "recentAssetIds": []
  },
  "distribution": {
    "origin": {
      "status": "active",
      "playbackId": "xYz789",
      "hlsUrl": "https://stream.mux.com/xYz789.m3u8"
    },
    "firstParty": {
      "status": "healthy",
      "pageUrl": "https://basefm.space",
      "note": null
    },
    "relays": [],
    "requiredRelayStatus": "healthy",
    "overallStatus": "healthy"
  },
  "streamHealth": "good",
  "pickupRecommended": false,
  "message": "Mux is active and the station should be ready for listeners."
}
```

| Field                 | Type           | Description                                                                                                                                                        |
| --------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `active`              | boolean        | Whether the caller has an active session                                                                                                                           |
| `session.id`          | number         | Session record ID                                                                                                                                                  |
| `session.djName`      | string \| null | DJ display name                                                                                                                                                    |
| `session.muxStreamId` | string         | Mux live stream ID                                                                                                                                                 |
| `session.playbackId`  | string \| null | Mux playback ID stored in the session                                                                                                                              |
| `session.status`      | string         | Session status (`active` or `live`)                                                                                                                                |
| `mux.id`              | string         | Mux live stream ID (from the Mux API)                                                                                                                              |
| `mux.status`          | string         | Real-time Mux stream status (`active`, `idle`, or `disabled`)                                                                                                      |
| `mux.playbackId`      | string \| null | Playback ID resolved from the Mux stream                                                                                                                           |
| `mux.recentAssetIds`  | array          | IDs of recently created Mux assets for this stream                                                                                                                 |
| `distribution`        | object         | Distribution state. See [distribution](#get-distribution-state) for the full shape.                                                                                |
| `streamHealth`        | string         | Overall health assessment: `good` (Mux active with playback), `waiting` (Mux idle or playback not yet available), or `bad` (Mux is not reporting a healthy stream) |
| `pickupRecommended`   | boolean        | `true` when the Mux stream is active but the session has not yet transitioned to `live` status, indicating a sync may be needed                                    |
| `message`             | string         | Human-readable explanation of the current stream health                                                                                                            |

### Response — no active session

```json theme={"dark"}
{
  "active": false,
  "message": "No active stream session found"
}
```

### Errors

| Code | Description                                                    |
| ---- | -------------------------------------------------------------- |
| 401  | Unauthorized — no valid session token or user session          |
| 403  | Forbidden — the session token does not match the session owner |
| 500  | Failed to read Mux status                                      |

## Sync stream status

```http theme={"dark"}
POST /api/basefm/streams/status
```

Manually syncs the DJ session status with the Mux stream. If the Mux stream is active, the session is transitioned to `live` and the playback ID is updated. Uses the same [authentication](#authentication) as the other session endpoints.

### Query parameters

| Parameter      | Type   | Required | Description                                                                                         |
| -------------- | ------ | -------- | --------------------------------------------------------------------------------------------------- |
| `sessionToken` | string | No       | Session access token from `POST /api/basefm/streams`. Alternative to the `x-basefm-session` header. |

### Headers

| Header             | Required | Description                                                              |
| ------------------ | -------- | ------------------------------------------------------------------------ |
| `x-basefm-session` | No       | Session access token. Alternative to the `sessionToken` query parameter. |

### Response — synced

```json theme={"dark"}
{
  "success": true,
  "synced": true,
  "muxStatus": "active",
  "playbackId": "xYz789",
  "streamHealth": "good",
  "message": "Mux stream is active. Session synced to live."
}
```

### Response — not yet active

```json theme={"dark"}
{
  "success": false,
  "synced": false,
  "muxStatus": "idle",
  "playbackId": null,
  "streamHealth": "waiting",
  "message": "Mux stream is not active yet. OBS ingest still needs to connect."
}
```

| Field          | Type           | Description                                                                                 |
| -------------- | -------------- | ------------------------------------------------------------------------------------------- |
| `success`      | boolean        | Whether the sync completed successfully                                                     |
| `synced`       | boolean        | Whether the session was actually updated                                                    |
| `muxStatus`    | string         | Real-time Mux stream status                                                                 |
| `playbackId`   | string \| null | Mux playback ID                                                                             |
| `streamHealth` | string         | Health assessment: `good` (active with playback), `waiting` (idle or no playback), or `bad` |
| `message`      | string         | Human-readable status explanation                                                           |

### Errors

| Code | Description                                                    |
| ---- | -------------------------------------------------------------- |
| 401  | Unauthorized — no valid session token or user session          |
| 403  | Forbidden — the session token does not match the session owner |
| 404  | No active stream session found                                 |
| 500  | Failed to sync stream from Mux                                 |

## Get distribution state

```http theme={"dark"}
GET /api/basefm/distribution
```

Returns the current distribution state for the baseFM station, including the origin stream health, first-party page status, and relay destination statuses. No authentication required.

The `distribution.origin.status` field reflects the live origin state:

* `active` — Mux is healthy and a DJ is currently broadcasting.
* `idle` — Mux is healthy but no DJ is live (for example, after a set ended). This is the normal off-air state and does not indicate a problem.
* `degraded` — Mux credentials are missing or the Mux API returned an error. The response falls back to cached session data.

### Response

```json theme={"dark"}
{
  "distribution": {
    "origin": {
      "status": "active",
      "playbackId": "xYz789",
      "hlsUrl": "https://stream.mux.com/xYz789.m3u8"
    },
    "firstParty": {
      "status": "healthy",
      "pageUrl": "https://basefm.space",
      "note": null
    },
    "relays": [
      {
        "key": "basefm-space",
        "name": "basefm.space",
        "type": "hls-consumer",
        "required": true,
        "enabled": true,
        "status": "healthy",
        "viewerUrl": "https://basefm.space",
        "probeUrl": "https://basefm.space",
        "note": null,
        "lastHealthyAt": "2026-04-11T01:00:00.000Z",
        "lastErrorAt": null,
        "lastErrorMessage": null
      }
    ],
    "requiredRelayStatus": "healthy",
    "overallStatus": "healthy"
  }
}
```

| Field                                    | Type           | Description                                                                                                                                                                          |
| ---------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `distribution.origin.status`             | string         | Origin stream status. `active` (a DJ is broadcasting), `idle` (Mux is healthy but no DJ is live — the normal off-air state), or `degraded` (Mux API or credentials are unavailable). |
| `distribution.origin.playbackId`         | string \| null | Mux playback ID for the origin stream                                                                                                                                                |
| `distribution.origin.hlsUrl`             | string \| null | HLS URL for the origin stream                                                                                                                                                        |
| `distribution.firstParty.status`         | string         | First-party page health: `pending`, `healthy`, `degraded`, `failed`, or `stopped`                                                                                                    |
| `distribution.firstParty.pageUrl`        | string         | URL of the first-party baseFM page                                                                                                                                                   |
| `distribution.firstParty.note`           | string \| null | Contextual message about the first-party page status                                                                                                                                 |
| `distribution.relays`                    | array          | List of configured relay destinations                                                                                                                                                |
| `distribution.relays[].key`              | string         | Unique relay identifier                                                                                                                                                              |
| `distribution.relays[].name`             | string         | Display name                                                                                                                                                                         |
| `distribution.relays[].type`             | string         | Relay type: `hls-consumer`, `rtmp`, `youtube`, or `custom`                                                                                                                           |
| `distribution.relays[].required`         | boolean        | Whether this relay is required for overall health                                                                                                                                    |
| `distribution.relays[].enabled`          | boolean        | Whether this relay is enabled                                                                                                                                                        |
| `distribution.relays[].status`           | string         | Relay health: `pending`, `healthy`, `degraded`, `failed`, or `stopped`                                                                                                               |
| `distribution.relays[].viewerUrl`        | string \| null | URL where viewers can access this relay                                                                                                                                              |
| `distribution.relays[].probeUrl`         | string \| null | URL used for health probing                                                                                                                                                          |
| `distribution.relays[].note`             | string \| null | Contextual message about relay status                                                                                                                                                |
| `distribution.relays[].lastHealthyAt`    | string \| null | ISO 8601 timestamp of the last successful health check                                                                                                                               |
| `distribution.relays[].lastErrorAt`      | string \| null | ISO 8601 timestamp of the last health check failure                                                                                                                                  |
| `distribution.relays[].lastErrorMessage` | string \| null | Error message from the last failed health check                                                                                                                                      |
| `distribution.requiredRelayStatus`       | string         | Aggregated status of all required relays                                                                                                                                             |
| `distribution.overallStatus`             | string         | Overall distribution health across origin, first-party page, and required relays                                                                                                     |

### Errors

| Code | Description                                            |
| ---- | ------------------------------------------------------ |
| 500  | Internal error and no cached session data is available |

<Note>When Mux credentials are missing or the Mux API is unreachable, the endpoint returns a degraded distribution state based on cached session data with HTTP status `200` (as long as cached data exists). A `500` is only returned when both the Mux API and the session cache are unavailable.</Note>

### Relay playback verification

When building the distribution state, the platform verifies that each relay destination is serving the current Agentbot playback ID. For relays with a configured probe URL, the platform fetches the relay's live API and checks whether any of the returned streams include the current Mux playback ID.

* If the relay's live API returns a stale or mismatched playback ID, the relay is marked `status: "degraded"` with `note: "Relay live API is not serving the current Agentbot playback id."`
* If the relay's live API includes the current playback ID, the relay stays `status: "healthy"` with `note: null`.

This verification ensures that downstream consumers (such as basefm.space) are actually serving the same stream that Agentbot is broadcasting, and prevents the distribution state from reporting a relay as healthy when its content is stale.

## List relay destinations

```http theme={"dark"}
GET /api/basefm/relays
```

Returns all configured relay destinations for the baseFM station. No authentication required. Default relay destinations are created automatically if they do not exist.

### Response

```json theme={"dark"}
{
  "relays": [
    {
      "key": "basefm-space",
      "name": "basefm.space",
      "type": "hls-consumer",
      "required": true,
      "enabled": true,
      "status": "healthy",
      "viewerUrl": "https://basefm.space",
      "probeUrl": "https://basefm.space",
      "note": null,
      "lastHealthyAt": "2026-04-11T01:00:00.000Z",
      "lastErrorAt": null,
      "lastErrorMessage": null
    }
  ]
}
```

See the [distribution](#get-distribution-state) endpoint for full field descriptions of relay objects.

### Errors

| Code | Description                       |
| ---- | --------------------------------- |
| 500  | Unable to load relay destinations |

## Create or update relay destination

```http theme={"dark"}
POST /api/basefm/relays
```

Creates a new relay destination or updates an existing one. Requires admin session authentication.

### Request body

| Field       | Type    | Required | Description                                                                    |
| ----------- | ------- | -------- | ------------------------------------------------------------------------------ |
| `key`       | string  | Yes      | Unique relay identifier. Used as the upsert key.                               |
| `name`      | string  | Yes      | Display name for the relay                                                     |
| `type`      | string  | No       | Relay type: `hls-consumer`, `rtmp`, `youtube`, or `custom` (default: `custom`) |
| `viewerUrl` | string  | No       | URL where viewers can access this relay                                        |
| `probeUrl`  | string  | No       | URL used for health probing. Defaults to `viewerUrl` if not provided.          |
| `required`  | boolean | No       | Whether this relay is required for overall health (default: `false`)           |
| `enabled`   | boolean | No       | Whether this relay is enabled (default: `true`)                                |

### Response

```json theme={"dark"}
{
  "relay": {
    "id": 1,
    "key": "youtube-main",
    "name": "YouTube Live",
    "type": "youtube",
    "required": false,
    "enabled": true,
    "status": "pending",
    "viewer_url": "https://youtube.com/live/abc123",
    "probe_url": "https://youtube.com/live/abc123"
  }
}
```

### Errors

| Code | Description                                                                  |
| ---- | ---------------------------------------------------------------------------- |
| 400  | `key` and `name` are required                                                |
| 403  | Forbidden — admin session required                                           |
| 500  | Unable to save relay destination                                             |
| 503  | Relay persistence requires the baseFM relay database migration to be applied |

## Probe relay destination

```http theme={"dark"}
POST /api/basefm/relays/:relayKey/probe
```

Runs a health check against a relay destination's probe URL and updates the relay's health status in the database. Requires admin session authentication.

### Path parameters

| Parameter  | Type   | Description                                      |
| ---------- | ------ | ------------------------------------------------ |
| `relayKey` | string | The unique key of the relay destination to probe |

### Response

```json theme={"dark"}
{
  "ok": true,
  "error": null,
  "relay": {
    "id": 1,
    "key": "basefm-space",
    "name": "basefm.space",
    "type": "hls-consumer",
    "required": true,
    "enabled": true,
    "status": "healthy",
    "viewer_url": "https://basefm.space",
    "probe_url": "https://basefm.space",
    "last_healthy_at": "2026-04-11T01:00:00.000Z",
    "last_error_at": null,
    "last_error_message": null
  }
}
```

| Field   | Type           | Description                          |
| ------- | -------------- | ------------------------------------ |
| `ok`    | boolean        | Whether the probe succeeded          |
| `error` | string \| null | Error message if the probe failed    |
| `relay` | object         | Updated relay record after the probe |

### Errors

| Code | Description                                                                  |
| ---- | ---------------------------------------------------------------------------- |
| 400  | Relay has no probe target configured                                         |
| 403  | Forbidden — admin session required                                           |
| 404  | Relay not found                                                              |
| 500  | Unable to probe relay destination                                            |
| 503  | Relay persistence requires the baseFM relay database migration to be applied |

## Generate video

```http theme={"dark"}
POST /api/generate-video
```

Generates a video and uploads it to blob storage. Requires session authentication. This endpoint has a 5-minute timeout.

### Request body

| Field              | Type   | Required         | Description                                                        |
| ------------------ | ------ | ---------------- | ------------------------------------------------------------------ |
| `type`             | string | Yes              | Video type. One of: `demo`, `marketing`, `screenshot`, `tutorial`. |
| `agentName`        | string | For `demo`       | Agent name for the demo video                                      |
| `agentDescription` | string | For `demo`       | Agent description for the demo video                               |
| `productName`      | string | For `marketing`  | Product name for the marketing video                               |
| `features`         | any    | For `marketing`  | Product features for the marketing video                           |
| `imageUrl`         | string | For `screenshot` | Image URL to animate                                               |
| `description`      | string | For `screenshot` | Description for the animation                                      |
| `topic`            | string | For `tutorial`   | Tutorial topic                                                     |
| `steps`            | any    | For `tutorial`   | Tutorial steps                                                     |

### Response

```json theme={"dark"}
{
  "url": "https://blob-storage-url/videos/1710806400000.mp4"
}
```

### Errors

| Code | Description                       |
| ---- | --------------------------------- |
| 400  | Invalid video type                |
| 401  | Unauthorized — session required   |
| 500  | Video generation or upload failed |

## Mux webhooks

```http theme={"dark"}
POST /api/webhooks/mux
```

Receives and processes Mux webhook events. This endpoint verifies the request signature using HMAC-SHA256 and rejects unsigned, expired, or tampered requests.

<Warning>This endpoint is intended to be called by Mux only. You must configure the `MUX_SIGNING_SECRET` (or `MUX_WEBHOOK_SECRET`) environment variable for signature verification. When the signing secret is not configured, all requests are rejected.</Warning>

### Headers

| Header          | Required | Description                                                        |
| --------------- | -------- | ------------------------------------------------------------------ |
| `mux-signature` | Yes      | Mux webhook signature in the format `t=<timestamp>,v1=<signature>` |

### Signature verification

The endpoint performs the following checks:

1. Rejects requests missing the `mux-signature` header (`401`)
2. Rejects requests with a timestamp older than 5 minutes to prevent replay attacks (`403`)
3. Computes HMAC-SHA256 over `<timestamp>.<body>` using the signing secret
4. Performs a timing-safe comparison of the computed signature against the provided signature (`403` on mismatch)

### Handled event types

| Event                            | Behavior                                                                                                                 |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `video.asset.ready`              | Archives HD assets (1080p or higher) longer than 15 minutes. Shorter or lower-resolution assets are queued for deletion. |
| `video.live_stream.active`       | Marks the corresponding DJ session as `live` in the database and updates the playback ID. Triggers social amplification. |
| `video.live_stream.connected`    | Same behavior as `video.live_stream.active`.                                                                             |
| `video.live_stream.idle`         | Transitions DJ sessions with status `live` back to `active` in the database. Triggers AI set summary generation.         |
| `video.live_stream.disconnected` | Same behavior as `video.live_stream.idle`.                                                                               |

<Note>The webhook handler now syncs DJ session state to the database when live stream events are received. This enables the session cache used by the [`GET /api/basefm/live`](#list-live-streams) endpoint for degraded-mode fallback.</Note>

### Response

```json theme={"dark"}
{
  "received": true
}
```

### Errors

| Code | Description                            |
| ---- | -------------------------------------- |
| 401  | Missing `mux-signature` header         |
| 403  | Invalid signature or expired timestamp |
| 500  | Webhook processing failed              |
