Skip to main content

List donations

Read the donation stickers received during the current broadcast. The donation event on the stream arrives in real time only once, so use this API to recover donations that arrived while your bot was down.

Method and pathGET /v1/live/donations
Permissiondonations.read
Success200
When not on air404 (OAPI_MNGR_0301)

A broadcast with many donations does not come back at once — 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/donations?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/donations"

# Next page — pass the nextCursor from the previous response
curl -H "Authorization: Bearer $SPOON_ACCESS_TOKEN" \
"$SPOON_BASE_URL/v1/live/donations?cursor=1024307"

Response​

{
"donations": [
{
"user": {
"id": "Ab1Cd2Ef3Gh4Ij5Kl6Mn7O",
"nickname": "SpoonListener"
},
"stickerName": "Heart",
"amount": 300,
"time": "2026-10-19T05:47:50.512Z"
}
],
"nextCursor": "1024307"
}
FieldTypeNullableDescription
donationsobject[]NoThe donations received during the current broadcast.
donations[].userobjectNoThe donor.
donations[].user.idstringNoIdentifies the donor. An encrypted value.
donations[].user.nicknamestringNoThe nickname.
donations[].stickerNamestringNoThe display name of the sticker, exactly as it appears in the app.
donations[].amountnumberNoThe total spoons of this donation. The same value as amount on the donation event.
donations[].timestringNoWhen the donation arrived. ISO-8601 UTC (Z).
nextCursorstringYesThe cursor for the next page. null means this is the last page.
Identify people by user.id

user.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 user.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 user is the same shape and the same id as user on the event stream, so you can merge this history with the donations you collected in real time under the same key.

Code​

// donations.ts — node --experimental-strip-types donations.ts

interface Donation {
user: { id: string; nickname: string };
stickerName: string;
amount: number;
time: string;
}

interface DonationsPage {
donations: Donation[];
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/donations`);
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 donations: ${res.status}`);

const page = (await res.json()) as DonationsPage;
all.push(...page.donations);
cursor = page.nextCursor; // null ends the loop
} while (cursor);

const total = all.reduce((sum, d) => sum + d.amount, 0);
console.log(`${all.length} donations this broadcast, ${total} spoons in total.`);

// Recovery after a restart — reprocess only what came after the last handled time
const lastProcessedTime = '2026-10-19T05:00:00.000Z';
const missed = all.filter((d) => d.time > lastProcessedTime);
console.log(`Reprocessing ${missed.length} missed donations.`);
node --experimental-strip-types donations.ts

Watch out​

It covers the current broadcast only. Once the broadcast ends its history is gone, and the next broadcast returns only its own donations. To keep totals across broadcasts, store them yourself.

There is no message field. The message sent along with a donation is carried only by the donation event. If you need it, collect it in real time and store it.

Use the event stream day to day. This API is for recovery — after reconnecting, pull the history to fill the gap, and when the same donation shows up on both sides, deduplicate by the combination of user.id and time.

Loop until nextCursor is null. Taking only the first page means seeing part of the history on a generous broadcast. Conversely, do not pass null back as ?cursor= — it is the signal to stop.

Errors​

HTTPdetailCodeWhat happenedWhat to do
400OAPI_MNGR_0103The cursor is invalid. It is not a nextCursor from a previous response.Start over from the first page without cursor.
401noneThe token is missing or invalid.Ask the DJ to authorize again.
403noneThe donations.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.