List listeners
Read the id and nickname of the listeners currently in the room. Use it when you need to
know who is present — raffles, attendance checks.
| Method and path | GET /v1/live/listeners |
| Permission | listeners.read |
| Success | 200 |
| When not on air | 404 (OAPI_MNGR_0301) |
Popular broadcasts have thousands of listeners, so the list comes back a page at a time and you follow a cursor.
export SPOON_BASE_URL="https://kr-openapi.spooncast.net"
export SPOON_ACCESS_TOKEN="{your access token}"
For the Japan region use https://jp-openapi.spooncast.net — see regions.
Request
GET /v1/live/listeners?cursor={cursor}
Headers
| Name | Required | Description |
|---|---|---|
Authorization | ✔ | Bearer {access_token}. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
cursor | string | Pass nextCursor from the previous response. Omit it for the first page. |
Request example
# First page
curl -H "Authorization: Bearer $SPOON_ACCESS_TOKEN" "$SPOON_BASE_URL/v1/live/listeners"
# Next page — pass the nextCursor from the previous response
curl -H "Authorization: Bearer $SPOON_ACCESS_TOKEN" \
"$SPOON_BASE_URL/v1/live/listeners?cursor=eyJpZCI6MX0"
Response
{
"listeners": [
{ "id": "Ab1Cd2Ef3Gh4Ij5Kl6Mn7O", "nickname": "SpoonListener" },
{ "id": "Pq8Rs9Tu0Vw1Xy2Za3Bc4D", "nickname": "RadioFan" }
],
"nextCursor": "eyJpZCI6MX0"
}
| Field | Type | Nullable | Description |
|---|---|---|---|
listeners | object[] | No | The listeners currently in the room. |
listeners[].id | string | No | Identifies the listener. An encrypted value. |
listeners[].nickname | string | No | The nickname. |
nextCursor | string | Yes | The cursor for the next page. null means this is the last page. |
idid is this person's unique identifier. The same person always gets the same value, so use
it as the key for attendance, running totals, and rankings. Do not identify people by
nickname — it changes and it repeats.
The value is scoped to your bot and that DJ. It survives a DJ disconnecting and granting consent again.
This id is the same value as user.id on the event stream. You can check whether the
person who just posted is still in the room, and pass it straight into listenerId on
the room right now.
Code
// listeners.ts — node --experimental-strip-types listeners.ts
interface Listener {
id: string;
nickname: string;
}
interface ListenersPage {
listeners: Listener[];
nextCursor: string | null;
}
const BASE = process.env.SPOON_BASE_URL;
const TOKEN = process.env.SPOON_ACCESS_TOKEN;
const all = [];
let cursor;
do {
const url = new URL(`${BASE}/v1/live/listeners`);
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}` } });
if (!res.ok) throw new Error(`Could not list listeners: ${res.status}`);
const page = (await res.json()) as ListenersPage;
all.push(...page.listeners);
cursor = page.nextCursor; // null ends the loop
} while (cursor);
console.log(`${all.length} people are listening.`);
// Pick one — the key for storage and matching is always id
const winner = all[Math.floor(Math.random() * all.length)];
console.log(`Winner: ${winner.nickname} (${winner.id})`);
node --experimental-strip-types listeners.ts
Watch out
The bot itself is in the list once it connects to the stream, because connecting makes it join
as a listener. Filter the bot's id out of raffles and tallies — the event stream leaves out the
bot's own posts, but this API does not.
Loop until nextCursor is null. Taking only the first page means seeing part of the
room on a busy broadcast. Conversely, do not pass null back as ?cursor= — it is the
signal to stop.
The list is a snapshot of the moment you called. People come and go while you page through it. If you need "was present for the whole broadcast", collect joins from the event stream instead and build it yourself.
Errors
| HTTP | detailCode | What happened | What to do |
|---|---|---|---|
401 | none | The token is missing or invalid. | Ask the DJ to authorize again. |
403 | none | The listeners.read permission is missing. | Add the permission and get consent again. |
404 | OAPI_MNGR_0301 | The DJ is not on air. | A state, not an error. Call again shortly. |
502 | OAPI_MNGR_2302 | Broadcast server error. | Retry after a backoff. |
States outside this table can arrive too. A wrong method or Content-Type
(400, 405, 415 — OAPI_MNGR_0103), the call quota (429, no body), and server errors
(500 — OAPI_MNGR_1002) are common to every endpoint — see
Error codes.
Related documents
- Endpoint list — the full endpoint list.
- Read the fan ranking — the top 30 donors.
- Event stream — chat and joins keyed by the same
id. - Error codes — the full list of error codes.