본문으로 건너뛰기

청취자 목록 조회

지금 방에 있는 청취자의 id 와 닉네임을 조회해요. 추첨 · 출석 확인처럼 "지금 누가 있는지" 가 필요할 때 써요.

메서드 · 경로GET /v1/live/listeners
필요한 권한listeners.read
성공 응답200
방송 중이 아닐 때404 (OAPI_MNGR_0301)

인기 방송은 청취자가 수천 명이라 한 번에 다 주지 않고 커서로 이어 받아요.

export SPOON_BASE_URL="https://kr-openapi.spooncast.net"
export SPOON_ACCESS_TOKEN="{발급받은 access token}"

일본 지역이면 https://jp-openapi.spooncast.net 이에요 — 지역별 주소.

요청​

GET /v1/live/listeners?cursor={cursor}

헤더​

이름필수설명
Authorization✔Bearer {access_token} 형식이에요.

쿼리 파라미터​

이름타입필수설명
cursorstring이전 응답의 nextCursor 를 그대로 넣어요. 첫 장은 생략해요.

요청 예시​

# 첫 장
curl -H "Authorization: Bearer $SPOON_ACCESS_TOKEN" "$SPOON_BASE_URL/v1/live/listeners"

# 다음 장 — 앞 응답의 nextCursor 를 그대로 넣어요
curl -H "Authorization: Bearer $SPOON_ACCESS_TOKEN" \
"$SPOON_BASE_URL/v1/live/listeners?cursor=eyJpZCI6MX0"

응답​

{
"listeners": [
{ "id": "Ab1Cd2Ef3Gh4Ij5Kl6Mn7O", "nickname": "스푼청취자" },
{ "id": "Pq8Rs9Tu0Vw1Xy2Za3Bc4D", "nickname": "라디오팬" }
],
"nextCursor": "eyJpZCI6MX0"
}
필드타입null설명
listenersobject[]아니오지금 방에 있는 청취자예요.
listeners[].idstring아니오청취자를 가리키는 값이에요. 암호화한 값이에요.
listeners[].nicknamestring아니오닉네임이에요.
nextCursorstring예다음 장의 커서예요. null 이면 마지막 장이에요.
id 로 사람을 식별해요

id 는 이 사람의 고유 식별자예요. 같은 사람이면 항상 같은 값이라 출석 · 누적 · 랭킹의 키로 쓰면 돼요. nickname 은 바뀌고 중복될 수 있으니 식별에 쓰지 마세요.

값의 범위는 내 봇과 그 DJ 조합이에요. DJ 가 연동을 끊었다 다시 동의해도 유지돼요.

이 id 는 이벤트 스트림의 user.id 와 같은 값이에요. 채팅을 친 사람이 지금도 방에 있는지 대조할 수 있어요.

코드​

// 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(`청취자 목록 조회 실패: ${res.status}`);

const page = (await res.json()) as ListenersPage;
all.push(...page.listeners);
cursor = page.nextCursor; // null 이면 반복이 끝나요
} while (cursor);

console.log(`지금 ${all.length}명이 듣고 있어요.`);

// 한 명을 뽑아요 — 저장·대조의 키는 언제나 id 예요
const winner = all[Math.floor(Math.random() * all.length)];
console.log(`추첨: ${winner.nickname} (${winner.id})`);
node --experimental-strip-types listeners.ts

주의​

스트림에 연결한 봇 자신도 목록에 있어요. 연결하는 순간 봇이 청취자로 입장하기 때문이에요. 추첨이나 집계에서는 봇의 id 를 걸러내야 해요 — 이벤트 스트림은 봇 자기 발화를 빼주지만 이 API 는 빼주지 않아요.

nextCursor 가 null 일 때까지 반복해요. 첫 장만 받고 끝내면 청취자가 많은 방송에서 일부만 보게 돼요. 반대로 null 을 그대로 ?cursor= 에 넣으면 안 돼요 — 반복을 멈추는 신호예요.

목록은 부르는 순간의 스냅샷이에요. 페이지를 넘기는 사이에 사람이 들어오고 나갈 수 있어요. "방송 내내 있었는지" 가 필요하면 이벤트 스트림의 입장 이벤트를 받아 직접 쌓아요.

에러​

HTTPdetailCode무슨 일인가요어떻게 하나요
401없음토큰이 없거나 무효해요.DJ 에게 재연동을 안내해요.
403없음listeners.read 권한이 없어요.권한을 추가해 재동의를 받아요.
404OAPI_MNGR_0301방송 중이 아니에요.오류가 아니라 상태예요. 잠시 뒤 다시 불러요.
502OAPI_MNGR_2302방송 서버 오류예요.백오프 후 재시도해요.

이 표에 없는 상태도 올 수 있어요. 잘못된 메서드·Content-Type(400 · 405 · 415, OAPI_MNGR_0103) · 호출 한도(429, 본문 없음) · 서버 오류(500, OAPI_MNGR_1002)는 모든 엔드포인트에 공통이에요 — 에러 코드에 있어요.

관련 문서​