Build with our archive.
Explore SceneSat and guest-station recordings, public track metadata, cue sheets and recorded conversations through one predictable API.
https://api.scenesat.com/v1Quick 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 scope | Permits |
|---|---|
catalog:read | Archive channels, published audio recordings, public tracks and cue sheets. |
chat:read | Replay 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.
Conventions
- HTTPS, UTF-8 JSON and
GET.HEADand CORSOPTIONSare supported. No session cookies are used. - Lists return
dataandpagination. The default page size is 50, maximum 100. PassnextCursorascursorwith the same filters until it isnull. - 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.
showIdidentifies the broadcast andchannelIdthe 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
| Request | Purpose | Key scope |
|---|---|---|
GET/v1/channels | List channels | catalog:read |
GET/v1/channels/{id} | Get one channel | catalog:read |
GET/v1/recordings | List recordings | catalog:read |
GET/v1/recordings/{id} | Get one recording | catalog:read |
GET/v1/tracks | List tracks | catalog:read |
GET/v1/tracks/{id} | Get one track | catalog:read |
GET/v1/recordings/{id}/cuesheet | Read a recording’s cue sheet | catalog:read |
GET/v1/recordings/{id}/chat | Read a recording’s replay chat | chat: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
}
}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
}
}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
}
}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"
}
}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
}
}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
}
}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
}
}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
}
}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.
| Status | Meaning |
|---|---|
400 | Invalid parameter or cursor. |
401 | A supplied key is invalid, expired or revoked. Omit Authorization to read anonymously. |
403 | The key lacks the required scope. |
404 | Unknown endpoint, missing resource or content that is not public. |
405 | Method is not supported; the API is read-only. |
429 | Request quota or ingress limit reached. |
503 | Temporary 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.