SceneSat API
Visit SceneSat ↗
The SceneSat archive · API v1

Build with our archive.

Explore SceneSat and guest-station recordings, public track metadata, cue sheets and recorded conversations through one predictable API.

Base URLhttps://api.scenesat.com/v1

Quick start

No account or API key is needed. Read public channels, recordings, tracklists and recorded chat directly from your application.

curl --fail-with-body \
  'https://api.scenesat.com/v1/channels?limit=50'

Browser and mobile apps can call this API directly without distributing a secret. Cross-origin requests are supported. The examples below use illustrative IDs.

{
    "data": [
        {
            "id": 1,
            "slug": "scenesat",
            "title": "SceneSat",
            "description": "SceneSat archive",
            "kind": "scenesat",
            "websiteUrl": "https://scenesat.com",
            "livestreamUrl": null
        }
    ],
    "pagination": {
        "limit": 50,
        "nextCursor": null
    }
}

Keys & access

All endpoints below allow anonymous reads at 30 requests per minute per IP address. An optional application key raises the allowance to 60 requests per minute per key. Both return the same public data. SceneSat operators can issue keys with an owner, expiry date and explicit scopes; self-service registration is not available yet.

Key scopePermits
catalog:readArchive channels, published audio recordings, public tracks and cue sheets.
chat:readReplay chat and host commentary for published audio recordings.

If you choose to use a key, send Authorization: Bearer <key>. It must have the endpoint's scope; invalid, expired or revoked keys return an error. Keys are shown once at issuance and can be revoked immediately. Keep them on your server; do not put them in URLs, browser bundles or shared examples. SceneSatID cookies and internal service tokens do not authenticate here.

# Optional: a higher allowance for a server-side integration
curl --fail-with-body \
  -H "Authorization: Bearer $SCENESAT_API_KEY" \
  'https://api.scenesat.com/v1/channels?limit=50'

SCENESAT_API_KEY is an example environment variable for your own application, only needed if you use a key.

The API is read-only. Catalog access does not grant rights to redistribute music or recordings. Private, restricted, offline and unpublished recordings are excluded. Track endpoints expose metadata; they do not provide track audio.

Conventions

  • HTTPS, UTF-8 JSON and GET. HEAD and CORS OPTIONS are supported. No session cookies are used.
  • Lists return data and pagination. The default page size is 50, maximum 100. Pass nextCursor as cursor with the same filters until it is null.
  • Channel, recording and track lists use ascending IDs. Cue sheets and chat use ascending offset, then ID. Cursors are opaque; live editorial changes can require a fresh scan.
  • Recording IDs identify files. showId identifies the broadcast and channelId the archive channel. Replacing an audio file changes its recording ID. These are not reLive IDs or audio playout channel names.
  • Dates use UTC ISO 8601. Durations and offsets are in seconds. Media links are absolute URLs on the media host.
  • Text is untrusted content: escape it when rendering HTML. Replay audience chat at its offset; commentary is identified separately.

This first version covers primary published audio recordings. Video, secondary recordings and account-specific media access will be added as separate capabilities.

Endpoints

RequestPurposeKey scope
GET/v1/channelsList channelscatalog:read
GET/v1/channels/{id}Get one channelcatalog:read
GET/v1/recordingsList recordingscatalog:read
GET/v1/recordings/{id}Get one recordingcatalog:read
GET/v1/tracksList trackscatalog:read
GET/v1/tracks/{id}Get one trackcatalog:read
GET/v1/recordings/{id}/cuesheetRead a recording’s cue sheetcatalog:read
GET/v1/recordings/{id}/chatRead a recording’s replay chatchat:read
GET/v1/channels

Anonymous access · Scope when using a key: catalog:read

An archive channel (SceneSat or a guest station). These numeric IDs identify archive channels; audio playout channels have a separate namespace of names. Anonymous access is allowed. Scope when using a key: catalog:read. Ordered by ID ascending.

Parameters

limit query · optional
Maximum items per page.

cursor query · optional
Opaque nextCursor from the previous page. Keep the same filters.

Example response

{
    "data": [
        {
            "id": 1,
            "slug": "scenesat",
            "title": "SceneSat",
            "description": "SceneSat archive",
            "kind": "scenesat",
            "websiteUrl": "https://scenesat.com",
            "livestreamUrl": null
        }
    ],
    "pagination": {
        "limit": 50,
        "nextCursor": null
    }
}

Channel schema · Error responses

GET/v1/channels/{id}

Anonymous access · Scope when using a key: catalog:read

An archive channel (SceneSat or a guest station). These numeric IDs identify archive channels; audio playout channels have a separate namespace of names. Anonymous access is allowed. Scope when using a key: catalog:read.

Parameters

id path · required
Manager archive channel ID. Not a reLive ID.

Example response

{
    "data": {
        "id": 1,
        "slug": "scenesat",
        "title": "SceneSat",
        "description": "SceneSat archive",
        "kind": "scenesat",
        "websiteUrl": "https://scenesat.com",
        "livestreamUrl": null
    }
}

Channel schema · Error responses

GET/v1/recordings

Anonymous access · Scope when using a key: catalog:read

A published primary audio recording, identified by its Manager file ID. showId identifies the broadcast; neither ID is a legacy reLive stream ID. Replacing the recording file changes the recording ID. Video and secondary recordings are not included in this initial collection. Anonymous access is allowed. Scope when using a key: catalog:read. Ordered by ID ascending.

Parameters

limit query · optional
Maximum items per page.

cursor query · optional
Opaque nextCursor from the previous page. Keep the same filters.

channelId query · optional
Filter by an archive channel ID.

Example response

{
    "data": [
        {
            "id": 700,
            "showId": 42,
            "channelId": 1,
            "medium": "audio",
            "title": "Example broadcast",
            "host": "Example host",
            "description": "An illustrative recording",
            "startedAt": "2026-09-20T16:00:00Z",
            "durationSeconds": 3600,
            "mediaUrl": "https://media.scenesat.com/asset/00000000-0000-4000-8000-000000000700"
        }
    ],
    "pagination": {
        "limit": 50,
        "nextCursor": null
    }
}

Recording schema · Error responses

GET/v1/recordings/{id}

Anonymous access · Scope when using a key: catalog:read

A published primary audio recording, identified by its Manager file ID. showId identifies the broadcast; neither ID is a legacy reLive stream ID. Replacing the recording file changes the recording ID. Video and secondary recordings are not included in this initial collection. Anonymous access is allowed. Scope when using a key: catalog:read.

Parameters

id path · required
Manager file ID. Not a reLive ID.

Example response

{
    "data": {
        "id": 700,
        "showId": 42,
        "channelId": 1,
        "medium": "audio",
        "title": "Example broadcast",
        "host": "Example host",
        "description": "An illustrative recording",
        "startedAt": "2026-09-20T16:00:00Z",
        "durationSeconds": 3600,
        "mediaUrl": "https://media.scenesat.com/asset/00000000-0000-4000-8000-000000000700"
    }
}

Recording schema · Error responses

GET/v1/tracks

Anonymous access · Scope when using a key: catalog:read

Public catalog metadata. This response grants no track audio streaming or download rights. Anonymous access is allowed. Scope when using a key: catalog:read. Ordered by ID ascending.

Parameters

limit query · optional
Maximum items per page.

cursor query · optional
Opaque nextCursor from the previous page. Keep the same filters.

Example response

{
    "data": [
        {
            "id": 17,
            "artist": "Example artist",
            "title": "Example track",
            "durationSeconds": 240,
            "explicit": false
        }
    ],
    "pagination": {
        "limit": 50,
        "nextCursor": null
    }
}

Track schema · Error responses

GET/v1/tracks/{id}

Anonymous access · Scope when using a key: catalog:read

Public catalog metadata. This response grants no track audio streaming or download rights. Anonymous access is allowed. Scope when using a key: catalog:read.

Parameters

id path · required
Manager track ID. Not a reLive ID.

Example response

{
    "data": {
        "id": 17,
        "artist": "Example artist",
        "title": "Example track",
        "durationSeconds": 240,
        "explicit": false
    }
}

Track schema · Error responses

GET/v1/recordings/{id}/cuesheet

Anonymous access · Scope when using a key: catalog:read

An as-aired tracklist entry. timingSource is an extensible label such as exact, provided, detected or interpolated; null means unknown. trackId is null for unmatched or non-public tracks. Offsets are not necessarily sample-accurate. Anonymous access is allowed. Scope when using a key: catalog:read. Ordered by offsetSeconds, then ID.

Parameters

id path · required
Manager file ID of a published recording.

limit query · optional
Maximum items per page.

cursor query · optional
Opaque nextCursor from the previous page. Keep the same filters.

Example response

{
    "data": [
        {
            "id": 25,
            "offsetSeconds": 125.5,
            "endSeconds": 365.5,
            "timingSource": "provided",
            "artist": "Example artist",
            "title": "Example track",
            "trackId": 17
        }
    ],
    "pagination": {
        "limit": 50,
        "nextCursor": null
    }
}

Cue schema · Error responses

GET/v1/recordings/{id}/chat

Anonymous access · Scope when using a key: chat:read

An edited archived replay message or host commentary line. type is an extensible label (message, action, join, part, topic, and others). No raw payload or platform account IDs are exposed. Text is untrusted plain text; escape it when displaying HTML. Replay audience messages at offsetSeconds rather than showing future conversation. Anonymous access is allowed. Scope when using a key: chat:read. Ordered by offsetSeconds, then ID.

Parameters

id path · required
Manager file ID of a published recording.

limit query · optional
Maximum items per page.

cursor query · optional
Opaque nextCursor from the previous page. Keep the same filters.

Example response

{
    "data": [
        {
            "id": 91,
            "offsetSeconds": 130,
            "channel": {
                "id": 8,
                "label": "SceneSat chat",
                "kind": "audience"
            },
            "type": "message",
            "author": "Listener",
            "text": "Hello!"
        }
    ],
    "pagination": {
        "limit": 50,
        "nextCursor": null
    }
}

ChatMessage schema · Error responses

Response schemas

Every response field and nullability is described in the OpenAPI document.

Channel
{
    "type": "object",
    "required": [
        "id",
        "slug",
        "title",
        "description",
        "kind",
        "websiteUrl",
        "livestreamUrl"
    ],
    "properties": {
        "id": {
            "type": "integer",
            "format": "int64",
            "minimum": 1
        },
        "slug": {
            "type": "string"
        },
        "title": {
            "type": "string"
        },
        "description": {
            "type": "string"
        },
        "kind": {
            "type": "string"
        },
        "websiteUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true
        },
        "livestreamUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true
        }
    },
    "description": "An archive channel (SceneSat or a guest station). These numeric IDs identify archive channels; audio playout channels have a separate namespace of names."
}
Recording
{
    "type": "object",
    "required": [
        "id",
        "showId",
        "channelId",
        "medium",
        "title",
        "host",
        "description",
        "startedAt",
        "durationSeconds",
        "mediaUrl"
    ],
    "properties": {
        "id": {
            "type": "integer",
            "format": "int64",
            "minimum": 1
        },
        "showId": {
            "type": "integer",
            "format": "int64",
            "minimum": 1
        },
        "channelId": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "nullable": true
        },
        "medium": {
            "type": "string",
            "enum": [
                "audio"
            ]
        },
        "title": {
            "type": "string"
        },
        "host": {
            "type": "string"
        },
        "description": {
            "type": "string"
        },
        "startedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
        },
        "durationSeconds": {
            "type": "number"
        },
        "mediaUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true
        }
    },
    "description": "A published primary audio recording, identified by its Manager file ID. showId identifies the broadcast; neither ID is a legacy reLive stream ID. Replacing the recording file changes the recording ID. Video and secondary recordings are not included in this initial collection."
}
Track
{
    "type": "object",
    "required": [
        "id",
        "artist",
        "title",
        "durationSeconds",
        "explicit"
    ],
    "properties": {
        "id": {
            "type": "integer",
            "format": "int64",
            "minimum": 1
        },
        "artist": {
            "type": "string"
        },
        "title": {
            "type": "string"
        },
        "durationSeconds": {
            "type": "number"
        },
        "explicit": {
            "type": "boolean"
        }
    },
    "description": "Public catalog metadata. This response grants no track audio streaming or download rights."
}
Cue
{
    "type": "object",
    "required": [
        "id",
        "offsetSeconds",
        "endSeconds",
        "timingSource",
        "artist",
        "title",
        "trackId"
    ],
    "properties": {
        "id": {
            "type": "integer",
            "format": "int64",
            "minimum": 1
        },
        "offsetSeconds": {
            "type": "number"
        },
        "endSeconds": {
            "type": "number",
            "nullable": true
        },
        "timingSource": {
            "type": "string",
            "nullable": true
        },
        "artist": {
            "type": "string"
        },
        "title": {
            "type": "string"
        },
        "trackId": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "nullable": true
        }
    },
    "description": "An as-aired tracklist entry. timingSource is an extensible label such as exact, provided, detected or interpolated; null means unknown. trackId is null for unmatched or non-public tracks. Offsets are not necessarily sample-accurate."
}
ChatMessage
{
    "type": "object",
    "required": [
        "id",
        "offsetSeconds",
        "channel",
        "type",
        "author",
        "text"
    ],
    "properties": {
        "id": {
            "type": "integer",
            "format": "int64",
            "minimum": 1
        },
        "offsetSeconds": {
            "type": "number"
        },
        "channel": {
            "type": "object",
            "required": [
                "id",
                "label",
                "kind"
            ],
            "properties": {
                "id": {
                    "type": "integer",
                    "format": "int64",
                    "minimum": 1
                },
                "label": {
                    "type": "string"
                },
                "kind": {
                    "type": "string",
                    "enum": [
                        "audience",
                        "commentary"
                    ]
                }
            }
        },
        "type": {
            "type": "string"
        },
        "author": {
            "type": "string"
        },
        "text": {
            "type": "string"
        }
    },
    "description": "An edited archived replay message or host commentary line. type is an extensible label (message, action, join, part, topic, and others). No raw payload or platform account IDs are exposed. Text is untrusted plain text; escape it when displaying HTML. Replay audience messages at offsetSeconds rather than showing future conversation."
}
Pagination
{
    "type": "object",
    "required": [
        "limit",
        "nextCursor"
    ],
    "properties": {
        "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
        },
        "nextCursor": {
            "type": "string",
            "nullable": true
        }
    },
    "description": "Pass nextCursor unchanged as cursor with the same endpoint and filters. Null means the final page. Collections are live views, not transactional snapshots; an export may need a fresh scan after editorial changes."
}
Error
{
    "type": "object",
    "required": [
        "error"
    ],
    "properties": {
        "error": {
            "type": "object",
            "required": [
                "code",
                "message",
                "requestId"
            ],
            "properties": {
                "code": {
                    "type": "string"
                },
                "message": {
                    "type": "string"
                },
                "requestId": {
                    "type": "string"
                }
            }
        }
    }
}

Errors & limits

Anonymous access allows 30 requests per UTC minute per IP address, shared across endpoints and devices on that public address. Optional keys allow 60 requests per UTC minute per key, independently of the anonymous quota. GET and HEAD both count. Documentation and OPTIONS do not use these minute quotas. An additional ingress limit protects the service. On 429 or 503, wait at least Retry-After seconds before retrying. Responses are not shared-cacheable.

StatusMeaning
400Invalid parameter or cursor.
401A supplied key is invalid, expired or revoked. Omit Authorization to read anonymously.
403The key lacks the required scope.
404Unknown endpoint, missing resource or content that is not public.
405Method is not supported; the API is read-only.
429Request quota or ingress limit reached.
503Temporary service failure.
{
    "error": {
        "code": "invalid_api_key",
        "message": "The supplied API key is invalid, expired or revoked.",
        "requestId": "example-correlation-id"
    }
}

Application responses include X-Request-Id. Successful data responses include X-RateLimit-Limit and X-RateLimit-Reset (Unix seconds). Include the request ID when reporting an error.

Versioning & reLive

The major version lives in the URL. Compatible additions stay in /v1; breaking changes will receive a new major version and a documented migration period. Clients should tolerate additional fields and new values for extensible labels.

This API replaces the old reLive integration model with paginated resources, optional keys and consistent HTTP errors. Archive channels replace the station listing, recordings replace streams, and cue sheets and chat are separate recording resources. Legacy reLive IDs must be mapped to Manager IDs; changing the hostname alone will not migrate an old client.

Existing SceneSat internal and reLive compatibility endpoints keep their current URLs and authentication. They are documented separately inside Korolev.