# 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.

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](/en/doc/2.5).

## Setup: API key and chat room id {#keys}

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 {#request}

| Item | Value |
| --- | --- |
| URL | `https://api.uchat.io/v2/rooms/{id}/...` |
| `Authorization` header | `Bearer {API key}` |
| Body | For `POST`, a JSON object (see [Send events](#events) below) |

| Method | Path | Permission | Plan | Description |
| --- | --- | --- | --- | --- |
| `GET` | `/v2/rooms/{id}` | Read | Paid | [Chat room summary](#room) |
| `GET` | `/v2/rooms/{id}/stats` | Read | Free | [User count](#stats) |
| `GET` | `/v2/rooms/{id}/users` | Read | Paid | [User list](#users) |
| `GET` | `/v2/rooms/{id}/messages` | Read | Paid | [Recent chats](#messages) |
| `POST` | `/v2/rooms/{id}/events` | Events | Free | [Send events](#events) |

## Responses and limits {#response}

On success, the result is in `data` (events return HTTP 202).

```json
{ "data": { "online": 15 } }
```

On failure, you get an HTTP status and `error`. See [Error codes](#errors) for the full list.

```json
{ "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, the `Retry-After` header 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 {#room}

`GET /v2/rooms/{id}` (paid)

```sh
curl https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx \
  -H "Authorization: Bearer uchat_..."
```

```json
{
	"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 {#stats}

`GET /v2/rooms/{id}/stats` (free)

```json
{
	"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 {#users}

`GET /v2/rooms/{id}/users` (paid)

```json
{
	"data": [
		{
			"nick": "Minji",
			"user_id": "minji",
			"role": "member",
			"level": 3,
			"device": "mobile",
			"connected_at": "2026-10-19T03:12:45.000Z"
		}
	]
}
```

- `role` is `guest`, `member`, `subadmin`, or `admin`. `user_id` is set only for people who joined through member integration; it is `null` for everyone else.
- IPs, sessions, and hidden connections are not included.

## Recent chats {#messages}

`GET /v2/rooms/{id}/messages?limit=50&before={message id}` (paid)

```json
{
	"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 {#events}

`POST /v2/rooms/{id}/events` (free)

```sh
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]}'
```

```json
{ "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.auth` is `4` and the nickname is empty. `data.type` is `0` for everyone and `1` for 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.
- **`save` has the same limits as [channel save](/en/doc/3.5#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.
- `save` and `to` cannot 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](/en/doc/3.5#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:

```js
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 {#errors}

| 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}

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** with `error_code`.
- `stats` returns 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 |
