# 서버 REST API

> 내 서버에서 채팅방 상태를 읽고 플러그인 채널로 데이터를 보내는 REST API(v2)의 인증, 엔드포인트, 응답, 오류 코드와 예전 v1 방식이에요.

서버 REST API로 내 서버에서 채팅방 상태(접속자 수·접속자 목록·최근 채팅)를 읽고, 플러그인 채널로 데이터를 보내요. 이 문서는 요청 형식·응답·오류 코드의 정본이에요. 따라 하며 써 보려면 [서버에서 채팅방으로 알림 보내기](/doc/2.5)를 보세요.

## 준비: API 키와 채팅방 아이디 {#keys}

관리 페이지의 **API** 메뉴에서 두 값을 확인해요.

| 값 | 모양 | 설명 |
| --- | --- | --- |
| API 채팅방 아이디 | `room_` + 소문자·숫자 16자 | 주소의 `{id}` 자리에 넣어요. 채팅방 아이디와 달리 바뀌지 않고 주소에 그대로 쓸 수 있어요 |
| API 키 | `uchat_` + 소문자·숫자 32자 | 발급할 때 **한 번만** 보여요. 잃어버리면 그 키를 다시 발급하거나 폐기하고 새로 발급해요 |

- 키마다 이름과 권한을 정해요. **조회**(`read`)는 GET 엔드포인트, **이벤트**(`events`)는 이벤트 보내기에 필요해요. 권한과 이름은 나중에 바꿀 수 있고, 바꾼 권한은 다음 호출부터 적용돼요.
- **값 다시 발급**은 이름·권한·사용 기록을 그대로 두고 키 값만 바꿔요. 옛 값은 바로 쓸 수 없어요.
- 채팅방마다 쓰는 중인 키는 20개까지예요.
- API 키는 서버에만 두세요. 브라우저에서 부르면 키가 드러나 누구나 채팅방에 데이터를 보낼 수 있게 돼요.
- 호출은 키마다 **API** 메뉴의 사용량·호출 기록에 30일 동안 남아요(요청 본문 앞 1KB와 IP 포함). 개인정보·결제정보를 그대로 보내지 마세요.
- **API** 메뉴의 **시험하기**에서 키 없이 엔드포인트를 실행해 실제 응답을 볼 수 있어요(호출 횟수에는 세지 않아요).

## 요청 형식 {#request}

| 항목 | 값 |
| --- | --- |
| 주소 | `https://api.uchat.io/v2/rooms/{id}/...` |
| `Authorization` 헤더 | `Bearer {API 키}` |
| 본문 | `POST`는 JSON 객체(아래 [이벤트 보내기](#events)) |

| 메서드 | 경로 | 권한 | 요금제 | 설명 |
| --- | --- | --- | --- | --- |
| `GET` | `/v2/rooms/{id}` | 조회 | 유료 | [채팅방 요약](#room) |
| `GET` | `/v2/rooms/{id}/stats` | 조회 | 무료 | [접속자 수](#stats) |
| `GET` | `/v2/rooms/{id}/users` | 조회 | 유료 | [접속자 목록](#users) |
| `GET` | `/v2/rooms/{id}/messages` | 조회 | 유료 | [최근 채팅](#messages) |
| `POST` | `/v2/rooms/{id}/events` | 이벤트 | 무료 | [이벤트 보내기](#events) |

## 응답과 한도 {#response}

성공하면 `data`에 결과가 담겨요(이벤트는 HTTP 202).

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

실패하면 HTTP 상태와 `error`가 와요. 전체 목록은 [오류 코드](#errors)예요.

```json
{ "error": { "code": "invalid_key", "message": "This API key is not valid for this room." } }
```

| 요금제 | 분당 | 하루(UTC) |
| --- | --- | --- |
| 무료 채팅방 | 60회 | 1,000회 |
| 유료 채팅방 | 600회 | 제한 없음 |

- 넘으면 HTTP 429(`rate_limited`, `daily_limit`)예요. 분당 한도는 `Retry-After` 헤더에 기다릴 초를 알려 줘요.
- 하루 횟수는 키가 맞은 호출만 세요. 키가 틀린 요청은 따로 분당 한도가 있어서, 남의 잘못된 요청이 내 한도를 쓰지 못해요.

## 채팅방 요약 {#room}

`GET /v2/rooms/{id}` (유료)

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

```json
{
	"data": {
		"id": "room_xxxxxxxxxxxxxxxx",
		"room_id": "my-room",
		"name": "내 채팅방",
		"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 }
	}
}
```

## 접속자 수 {#stats}

`GET /v2/rooms/{id}/stats` (무료)

```json
{
	"data": {
		"online": 15,
		"roles": { "guest": 3, "member": 10, "subadmin": 1, "admin": 1 },
		"devices": { "pc": 9, "mobile": 6 }
	}
}
```

접속자가 없어도 오류 없이 모두 0이에요.

## 접속자 목록 {#users}

`GET /v2/rooms/{id}/users` (유료)

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

- `role`은 `guest`·`member`·`subadmin`·`admin`이에요. `user_id`는 회원 연동으로 들어온 사람만 있고 나머지는 `null`이에요.
- IP·세션과 숨은 접속은 주지 않아요.

## 최근 채팅 {#messages}

`GET /v2/rooms/{id}/messages?limit=50&before={메시지 id}` (유료)

```json
{
	"data": [
		{
			"id": "bfd6a342c0f549beb12f4a1c4e05831c",
			"type": "message",
			"nick": "민지",
			"user_id": null,
			"role": "guest",
			"text": "안녕하세요",
			"created_at": "2026-10-19T03:15:02.000Z"
		}
	]
}
```

| 값 | 설명 |
| --- | --- |
| `limit` | 1~100, 기본 50. 새것부터 와요 |
| `before` | 이 메시지 `id`보다 오래된 것부터. 다음 쪽을 볼 때 마지막 메시지의 `id`를 넣어요 |
| `type` | `message`(채팅) 또는 `notice`(공지) |
| `id` | 웹훅 알림의 이벤트 id와 같은 값(32자 16진수) |

- 모두에게 보인 채팅과 공지만 와요. 귓속말, 지운 채팅, AI가 가린 채팅은 빠져요.
- 채팅 서버가 묶어서 저장하므로 방금 한 채팅은 몇 초 늦게 보일 수 있어요.

## 이벤트 보내기 {#events}

`POST /v2/rooms/{id}/events` (무료)

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

| 키 | 설명 |
| --- | --- |
| `plugin` | 받을 채널 이름(영문·숫자·`_`·`-` 1~64자) |
| `data` | 값 배열(20칸, 합쳐서 2KB까지). 문자열·숫자·불리언만 쓰고, 받는 쪽에는 모두 **문자열**로 가요. 줄바꿈 같은 제어 문자는 넣을 수 없어요 |
| `to` | (선택) 닉네임. 그 사람에게만 보내요 |
| `save` | (선택) `true`면 그 채널의 마지막 값으로 저장해 나중에 들어오는 사람에게도 보내요. 빈 `data`를 저장하면 지워요 |

- 받는 쪽은 `room.plugin.add('{plugin}').onReceived`예요. `data.user.auth`가 `4`이고 닉네임은 비어 있어요. `data.type`은 모두에게 `0`, 한 사람에게·저장값은 `1`이에요.
- 접속자가 없어도 성공이에요(받을 사람이 없을 뿐이에요). `save`는 이때도 저장돼서 다음에 들어온 사람이 받아요.
- **`save`의 한도는 [채널 저장](/doc/3.5#save)과 같아요.** 무료 채팅방은 채널 하나·1KB, 유료 채팅방은 채널마다 4KB·합쳐서 60KB예요. 무료 채팅방에서 다른 채널을 저장하면 앞의 채널은 지워져요. 저장값은 채팅방이 비거나 서버가 다시 시작돼도 남아요.
- `save`와 `to`는 함께 쓸 수 없어요.
- 무료 채팅방은 페이지 스크립트의 전송과 같은 1분 한도를 나눠 써요. 넘으면 `chat_rate_limited`(429)예요. [채널 API와 한도](/doc/3.5#limits)
- 유챗 플러그인 중 이벤트 알림창·후원 목표 막대·방송 상태·점수판·카운트다운·순위표는 이 API로 보낸 값을 그려요. 보내는 형식은 각 플러그인 안내에 있어요.

받는 쪽 코드:

```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; // 서버가 보낸 것만
		if (data.data[0] === 'paid') room.print('결제가 완료되었습니다: ' + data.data[1]);
	});
});
```

## 오류 코드 {#errors}

| HTTP | `code` | 뜻 |
| --- | --- | --- |
| 400 | `invalid_limit`, `invalid_before` | `messages`의 값이 형식에 맞지 않음 |
| 400 | `invalid_json`, `invalid_body` | 본문이 JSON 객체가 아님 |
| 400 | `invalid_plugin`, `invalid_to`, `invalid_save` | `plugin`·`to`·`save` 값이 형식에 맞지 않음 |
| 400 | `invalid_data`, `data_too_large` | `data`가 배열이 아니거나, 값 종류·제어 문자·칸 수·크기가 맞지 않음 |
| 400 | `save_with_to` | `save`와 `to`를 함께 씀 |
| 400 | `state_too_large` | 저장 한도를 넘음 |
| 401 | `missing_key` | `Authorization: Bearer` 헤더가 없음 |
| 401 | `invalid_key` | 이 채팅방의 키가 아니거나 폐기한 키 |
| 403 | `insufficient_scope` | 키에 이 엔드포인트의 권한(조회·이벤트)이 없음 |
| 403 | `paid_plan_required` | 유료 채팅방 전용 엔드포인트 |
| 404 | `room_not_found` | API 채팅방 아이디가 없음 |
| 404 | `target_not_found` | `to`의 닉네임이 채팅방에 없음 |
| 429 | `rate_limited`, `daily_limit` | 분당·하루 호출 한도 초과 |
| 429 | `chat_rate_limited` | 무료 채팅방 채널 전송 한도 초과 |
| 503 | `chat_server_unavailable` | 채팅 서버가 답하지 않음. 잠시 뒤 다시 해 보세요 |

## 예전 방식(v1) {#v1}

예전에 만든 연동을 위해 v1도 그대로 동작해요. 새로 만들 때는 v2를 쓰세요. v1은 같은 API 키로 인증하고(예전 API 토큰은 **API** 메뉴에 "v1 키"로 옮겨져 있어요), 호출 기록에도 남아요.

| 항목 | 값 |
| --- | --- |
| 주소 | `https://api.uchat.io/rooms/{채팅방 아이디}/stats`, `.../plugin/{채널}`, `.../plugin/{채널}/{닉네임}` |
| `User-Agent` 헤더 | 정확히 `uchat_api_client_v1`(아니면 HTTP 404 `{"status":"fail","msg":"unknown_api_version"}`) |
| `X-UCHAT-API-TOKEN` 헤더 | API 키 |
| `X-UCHAT-API-ROOM` 헤더 | 채팅방 아이디. 주소에 `/rooms/{채팅방 아이디}`를 넣지 않을 때만 |
| 본문 | `POST`는 JSON 배열 |

- 성공은 HTTP 200 `{"success":true,"status":"success","data":...}`, 실패는 **언제나 HTTP 500**과 `error_code`예요.
- `stats`는 접속자가 없으면 모두 0이고, 채널 전송은 접속자가 없어도 성공이에요(한 사람에게 보내면 404).
- `save`는 없어요.

| `error_code` | 뜻 |
| --- | --- |
| `1` | 키가 맞지 않거나 폐기한 키, 또는 없는 채팅방 |
| `2` | 채팅방 아이디가 없음 |
| `4`, `5` | 없는 경로, 지원하지 않는 메서드 |
| `6` | `X-UCHAT-API-TOKEN` 헤더가 없음 |
| `7`, `8`, `10` | 채널 이름이 없음, 본문이 비어 있음, 본문이 배열이 아님 |
| `11` | 키에 이 API의 권한이 없음(`stats`는 조회, `plugin`은 이벤트) |
| `101` | 채팅 서버와의 통신 형식 오류 |
| `404` | 대상 닉네임이 채팅방에 없음 |
| `428` | 무료 채팅방 채널 전송 한도 초과 |
| `0` | 그 밖의 오류 |
