Server REST API
Authentication, endpoints, responses, and error codes for the REST API (v2) that lets your server read a chat room's state and send data to plugin channels, plus the older v1 method.
Last updated October 19, 2026
On this page
With the server REST API, your server can read a chat room's state (user count, user list, recent chats) and send data to plugin channels. This page is the authoritative reference for the request format, responses, and error codes. For a step-by-step walkthrough, see Sending notifications from your server to a chat room.
Setup: API key and chat room id
Find these two values in the API menu of the admin page.
| Value | Format | Description |
|---|---|---|
| API chat room id | room_ + 16 lowercase letters and digits |
Goes in the {id} slot of the URL. Unlike the chat room ID, it never changes and can be used in a URL as is |
| API key | uchat_ + 32 lowercase letters and digits |
Shown only once when issued. If you lose it, roll that key or revoke it and issue a new one |
- Each key has a name and permissions. Read (
read) is needed for GET endpoints, and Events (events) for sending events. You can change the permissions and name later; changed permissions apply from the next call. - Roll key changes only the key value and keeps the name, permissions, and usage history. The old value stops working immediately.
- Each chat room can have up to 20 active keys.
- Keep API keys only on your server. If you call the API from a browser, the key is exposed and anyone can send data to your chat room.
- Calls are kept per key for 30 days in the usage and call log of the API menu (including the first 1KB of the request body and the IP). Do not send personal or payment information as-is.
- With Try it in the API menu, you can run endpoints without a key and see real responses (these calls are not counted).
Request format
| Item | Value |
|---|---|
| URL | https://api.uchat.io/v2/rooms/{id}/... |
Authorization header |
Bearer {API key} |
| Body | For POST, a JSON object (see Send events below) |
| Method | Path | Permission | Plan | Description |
|---|---|---|---|---|
GET |
/v2/rooms/{id} |
Read | Paid | Chat room summary |
GET |
/v2/rooms/{id}/stats |
Read | Free | User count |
GET |
/v2/rooms/{id}/users |
Read | Paid | User list |
GET |
/v2/rooms/{id}/messages |
Read | Paid | Recent chats |
POST |
/v2/rooms/{id}/events |
Events | Free | Send events |
Responses and limits
On success, the result is in data (events return HTTP 202).
{ "data": { "online": 15 } }
On failure, you get an HTTP status and error. See Error codes for the full list.
{ "error": { "code": "invalid_key", "message": "This API key is not valid for this room." } }
| Plan | Per minute | Per day (UTC) |
|---|---|---|
| Free chat room | 60 calls | 1,000 calls |
| Paid chat room | 600 calls | Unlimited |
- Going over returns HTTP 429 (
rate_limited,daily_limit). For the per-minute limit, theRetry-Afterheader tells you how many seconds to wait. - The daily count includes only calls with a valid key. Requests with a wrong key have their own per-minute limit, so someone else's bad requests cannot use up your limit.
Chat room summary
GET /v2/rooms/{id} (paid)
curl https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx \
-H "Authorization: Bearer uchat_..."
{
"data": {
"id": "room_xxxxxxxxxxxxxxxx",
"room_id": "my-room",
"name": "My chat room",
"channel": false,
"plan": "paid",
"paid": true,
"created_at": "2026-10-11T01:59:53.000Z",
"online": 15,
"settings": { "frozen": false, "join_password": false, "chat_length_limit": 100 }
}
}
User count
GET /v2/rooms/{id}/stats (free)
{
"data": {
"online": 15,
"roles": { "guest": 3, "member": 10, "subadmin": 1, "admin": 1 },
"devices": { "pc": 9, "mobile": 6 }
}
}
Even when no one is connected, you get all zeros instead of an error.
User list
GET /v2/rooms/{id}/users (paid)
{
"data": [
{
"nick": "Minji",
"user_id": "minji",
"role": "member",
"level": 3,
"device": "mobile",
"connected_at": "2026-10-19T03:12:45.000Z"
}
]
}
roleisguest,member,subadmin, oradmin.user_idis set only for people who joined through member integration; it isnullfor everyone else.- IPs, sessions, and hidden connections are not included.
Recent chats
GET /v2/rooms/{id}/messages?limit=50&before={message id} (paid)
{
"data": [
{
"id": "bfd6a342c0f549beb12f4a1c4e05831c",
"type": "message",
"nick": "Minji",
"user_id": null,
"role": "guest",
"text": "Hello",
"created_at": "2026-10-19T03:15:02.000Z"
}
]
}
| Value | Description |
|---|---|
limit |
1–100, default 50. Newest first |
before |
Starts from messages older than this message id. To get the next page, pass the id of the last message |
type |
message (chat) or notice (notice) |
id |
Same value as the event id in webhook notifications (32 hex characters) |
- Only chats and notices visible to everyone are included. Whispers, deleted chats, and chats hidden by AI are left out.
- The chat server saves in batches, so a chat sent just now may show up a few seconds late.
Send events
POST /v2/rooms/{id}/events (free)
curl -X POST https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events \
-H "Authorization: Bearer uchat_..." \
-H "Content-Type: application/json" \
-d '{"plugin":"payment","data":["paid","order-1001",15000]}'
{ "data": { "sent": true } }
| Key | Description |
|---|---|
plugin |
Name of the receiving channel (1–64 English letters, digits, _, -) |
data |
Array of values (20 slots, up to 2KB in total). Use only strings, numbers, and booleans; the receiving side gets them all as strings. Control characters such as line breaks are not allowed |
to |
(Optional) A nickname. Sends only to that person |
save |
(Optional) If true, saves it as the channel's last value and also sends it to people who join later. Saving an empty data deletes it |
- The receiving side is
room.plugin.add('{plugin}').onReceived.data.user.authis4and the nickname is empty.data.typeis0for everyone and1for one person or a saved value. - It succeeds even when no one is connected (there is just no one to receive it). With
save, the value is still saved, and the next person who joins receives it. savehas the same limits as channel save. A free chat room can save one channel up to 1KB; a paid chat room can save 4KB per channel and 60KB in total. In a free chat room, saving a different channel deletes the previous one. Saved values remain even when the chat room empties or the server restarts.saveandtocannot be used together.- Free chat rooms share the same one-minute limit with channel sends from page scripts. Going over returns
chat_rate_limited(429). Channel API and limits - Among uchat plugins, Event alert box, Donation goal bar, Live status, Scoreboard, Countdown, and Ranking display values sent with this API. The format to send is in each plugin's guide.
Receiving code:
U.chat('*').on('after.create#payment', function (room) {
if (room.id !== 'my-room') return;
room.plugin.add('payment').onReceived(function (room, data) {
if (!data.user || data.user.auth !== 4) return; // only data sent by the server
if (data.data[0] === 'paid') room.print('Payment complete: ' + data.data[1]);
});
});
Error codes
| HTTP | code |
Meaning |
|---|---|---|
| 400 | invalid_limit, invalid_before |
A messages value has the wrong format |
| 400 | invalid_json, invalid_body |
The body is not a JSON object |
| 400 | invalid_plugin, invalid_to, invalid_save |
A plugin, to, or save value has the wrong format |
| 400 | invalid_data, data_too_large |
data is not an array, or its value types, control characters, slot count, or size are invalid |
| 400 | save_with_to |
save and to were used together |
| 400 | state_too_large |
The save limit was exceeded |
| 401 | missing_key |
No Authorization: Bearer header |
| 401 | invalid_key |
Not a key for this chat room, or a revoked key |
| 403 | insufficient_scope |
The key lacks the permission (Read or Events) for this endpoint |
| 403 | paid_plan_required |
Endpoint for paid chat rooms only |
| 404 | room_not_found |
The API chat room id does not exist |
| 404 | target_not_found |
The to nickname is not in the chat room |
| 429 | rate_limited, daily_limit |
Per-minute or daily call limit exceeded |
| 429 | chat_rate_limited |
Free chat room channel send limit exceeded |
| 503 | chat_server_unavailable |
The chat server did not respond. Try again shortly |
Older method (v1)
v1 still works for integrations built earlier. Use v2 for anything new. v1 authenticates with the same API keys (old API tokens have been moved to the API menu as "v1 key") and its calls are also recorded in the call log.
| Item | Value |
|---|---|
| URL | https://api.uchat.io/rooms/{chat room ID}/stats, .../plugin/{channel}, .../plugin/{channel}/{nickname} |
User-Agent header |
Exactly uchat_api_client_v1 (otherwise HTTP 404 {"status":"fail","msg":"unknown_api_version"}) |
X-UCHAT-API-TOKEN header |
API key |
X-UCHAT-API-ROOM header |
Chat room ID. Only when the URL does not include /rooms/{chat room ID} |
| Body | For POST, a JSON array |
- Success is HTTP 200
{"success":true,"status":"success","data":...}; failure is always HTTP 500 witherror_code. statsreturns all zeros when no one is connected, and channel sends succeed even when no one is connected (sending to one person returns 404).- There is no
save.
error_code |
Meaning |
|---|---|
1 |
The key does not match or was revoked, or the chat room does not exist |
2 |
No chat room ID |
4, 5 |
Unknown path, unsupported method |
6 |
X-UCHAT-API-TOKEN header is missing |
7, 8, 10 |
No channel name, empty body, body is not an array |
11 |
The key lacks the permission for this API (Read for stats, Events for plugin) |
101 |
Communication format error with the chat server |
404 |
The target nickname is not in the chat room |
428 |
Free chat room channel send limit exceeded |
0 |
Other errors |
Still not sure how it works? Ask your question on the contact board.