Send chat
Post chat to a broadcast under the bot account's name. It shows up in the Spoon app chat with the bot account's nickname.
| Method and path | POST /v1/live/chat |
| Permission | chat.send |
| Success | 204 (no body) |
| When not on air | 404 (OAPI_MNGR_0301) |
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
POST /v1/live/chat
Headers
| Name | Required | Description |
|---|---|---|
Authorization | ✔ | Bearer {access_token}. |
Content-Type | ✔ | application/json. |
Body
{
"message": "Hello! Your bot says hi"
}
| Field | Type | Nullable | Description |
|---|---|---|---|
message | string | No | The chat message to send. Up to 200 characters. Empty or whitespace-only gives a 400. |
The token decides which broadcast this goes to. Nothing in the path or the body points at a broadcast.
Request example
curl -i -X POST "$SPOON_BASE_URL/v1/live/chat" \
-H "Authorization: Bearer $SPOON_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"message":"Hello! Your bot says hi"}'
Response
On success you get a 204 with no body. There is nothing to parse — read the status
code only.
HTTP/2 204
Code
// send.ts — node --experimental-strip-types send.ts "your message"
interface ApiError {
detailCode: string;
message: string;
}
const res = await fetch(`${process.env.SPOON_BASE_URL}/v1/live/chat`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SPOON_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: process.argv[2] }),
});
if (res.status === 204) {
console.log('Sent.');
} else if (res.status === 404) {
console.log('Not on air.');
} else if (res.status === 429) {
console.log('Chat is busy right now. Try again shortly.');
} else if (res.status === 401) {
console.error('The integration is disconnected. Ask the DJ to authorize again.');
} else {
// A 403 for a missing permission is blocked in front, so it has no detailCode
const { detailCode, message } = (await res.json()) as Partial<ApiError>;
console.error(`Send failed ${res.status} ${detailCode ?? 'insufficient scope'}: ${message ?? ''}`);
}
node --experimental-strip-types send.ts "Hello"
Watch out
Your own messages do not come back on your stream. The event stream does not return a bot's own posts to that bot, so it cannot re-run its own greeting as a command. Posts from other bots in the same broadcast are visible.
Over 200 characters is a 400. The count is in UTF-16 code units, so a single emoji
can count as two.
A 429 is not only about your own rate. The send limit is shared across the whole
broadcast, so busy listener chat can trip your bot when it has done nothing. Slowing your
own sending will not clear it immediately — wait a moment and try again.
If the DJ blocks you, the bot is blocked. Freezing chat (isChatFrozen) or chat-banning
the bot gives a 403, and a kick stops it joining at all. Checking isChatFrozen through
Read the current broadcast before posting avoids the failure up front.
You can post without connecting to the stream. If the bot is not in the room, the server joins it first.
Errors
| HTTP | detailCode | What happened | What to do |
|---|---|---|---|
400 | OAPI_MNGR_0103 | The request is malformed — a broken body, a missing required field, a missing Content-Type, or the wrong method. | Fix the request format. Sending the same request again gives the same result. |
400 | OAPI_MNGR_0108 | message is empty or only whitespace. | Check what you are sending. |
400 | OAPI_MNGR_0109 | Longer than 200 characters. | Shorten it. |
401 | none | The token is missing or invalid. | Ask the DJ to authorize again. |
403 | none | The chat.send permission is missing. | Add the permission and get consent again. |
403 | OAPI_MNGR_0208 | Chat is frozen, or the bot is chat-banned. | The two are not distinguished. Retry shortly, and tell the DJ if it keeps failing. |
403 | OAPI_MNGR_0209 | The bot cannot join the broadcast. The DJ may have blocked it. | Retrying does not help. The DJ has to lift it. |
404 | OAPI_MNGR_0301 | The DJ is not on air. | Wait until the broadcast starts. |
429 | OAPI_MNGR_0302 | You are sending chat too frequently. | Wait a moment and send again. |
502 | OAPI_MNGR_2302 | Broadcast server error. It happens at the broadcast check and join step before sending. | Retry after a backoff. |
502 | OAPI_MNGR_2303 | Chat server error. | Retry after a backoff. |
There are two kinds of 429. The OAPI_MNGR_0302 above (the chat send limit) has a
body, but hitting the API call quota gives you no body and no detailCode. Branch on the
status code before you parse the body, or it throws — the example above is written that way.
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 current broadcast — check
isChatFrozenbefore posting. - Event stream — receive listener reactions and reply.
- Error codes — the full list of error codes.