Skip to main content

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 pathGET /v1/live/listeners
Permissionlisteners.read
Success200
When not on air404 (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​

NameRequiredDescription
Authorization✔Bearer {access_token}.

Query parameters​

NameTypeRequiredDescription
cursorstringPass 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"
}
FieldTypeNullableDescription
listenersobject[]NoThe listeners currently in the room.
listeners[].idstringNoIdentifies the listener. An encrypted value.
listeners[].nicknamestringNoThe nickname.
nextCursorstringYesThe cursor for the next page. null means this is the last page.
Identify people by id

id 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​

HTTPdetailCodeWhat happenedWhat to do
401noneThe token is missing or invalid.Ask the DJ to authorize again.
403noneThe listeners.read permission is missing.Add the permission and get consent again.
404OAPI_MNGR_0301The DJ is not on air.A state, not an error. Call again shortly.
502OAPI_MNGR_2302Broadcast 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.