Оформление
Справочник маршрутов
Базовый адрес — https://emotify.ru/api/v1. Все тела — JSON, заголовок Content-Type: application/json обязателен у запросов с телом.
Авторизация — Authorization: Bot <ключ> (подробнее). Маршруты /users/@me… работают с токеном пользователя (вход через Emotify).
Все маршруты
Колонка «Scope» — какой доступ нужно отметить при выпуске ключа. Без него маршрут отвечает 403, даже когда роль бота на канале действие разрешает.
| Метод | Путь | Scope | Что делает |
|---|---|---|---|
GET | /me | любой ключ | Кто я и где я стою |
POST | /rooms/:roomId/messages | messages:write | Отправить сообщение |
GET | /rooms/:roomId/messages | messages:read | История комнаты |
PATCH | /rooms/:roomId/messages/:messageId | messages:write | Изменить своё сообщение |
DELETE | /rooms/:roomId/messages/:messageId | messages:write | Удалить своё сообщение |
POST | /rooms/:roomId/messages/:messageId/reactions | messages:write | Поставить реакцию |
DELETE | /rooms/:roomId/messages/:messageId/reactions | messages:write | Снять реакцию |
GET | /channels/:channelId | channel:read | Сведения о канале |
GET | /channels/:channelId/rooms | channel:read | Комнаты канала |
GET | /channels/:channelId/members | channel:read | Участники канала |
POST | /rooms/:roomId/voice | voice | Войти в голосовую |
DELETE | /rooms/:roomId/voice | voice | Выйти из голосовой |
GET | /events/token | events | Токен подписки на события |
PUT | /webhook | events | Задать адрес вебхука |
GET | /webhook | events | Состояние вебхука |
DELETE | /webhook | events | Удалить вебхук |
POST | /rooms/:roomId/messages/:messageId/pin | moderation | Закрепить сообщение |
DELETE | /rooms/:roomId/messages/:messageId/pin | moderation | Открепить сообщение |
PUT | /channels/:channelId/members/:userId/chat-mute | moderation | Запретить писать в чат |
DELETE | /channels/:channelId/members/:userId/chat-mute | moderation | Снять запрет на чат |
PUT | /channels/:channelId/members/:userId/voice-mute | moderation | Замьютить микрофон |
DELETE | /channels/:channelId/members/:userId/voice-mute | moderation | Снять мут микрофона |
DELETE | /channels/:channelId/members/:userId/voice | moderation | Отключить от голосовой |
PUT | /channels/:channelId/members/:userId/room | moderation | Перенести в другую комнату |
DELETE | /channels/:channelId/members/:userId | moderation | Исключить с канала |
GET | /commands | commands | Что объявлено сейчас |
PUT | /commands | commands | Объявить команды |
GET | /users/@me | токен пользователя | Профиль вошедшего человека |
GET | /users/@me/channels | токен пользователя | Каналы вошедшего человека |
Профиль бота
GET /me
Кто вы и на каких каналах установлены. Хороший первый запрос: им проверяют ключ.
bash
curl https://emotify.ru/api/v1/me -H "Authorization: Bot $BOT_TOKEN"Ответ 200:
json
{
"application": {
"id": "16c6e6b1-2b8d-4665-ba47-3d197a4a6029",
"name": "Пример бота",
"owner": { "id": "b49c3095-…", "name": "Z3oM" }
},
"bot": {
"id": "a2d78dd9-…",
"name": "Пример бота",
"avatar": "default_avatar.jpg",
"avatarVersion": 1
},
"channels": [
{
"id": "4facb875-…",
"name": "Тестовый сервак",
"role": { "systemName": "CHANNEL_BOT", "name": "Бот" }
}
]
}channels — каналы, где приложение установлено. Пустой список означает, что бота ещё никуда не добавили.
Канал
GET /channels/{channelId}
Название и описание канала.
bash
curl https://emotify.ru/api/v1/channels/$CHANNEL -H "Authorization: Bot $BOT_TOKEN"Ответ 200:
json
{
"id": "4facb875-…",
"name": "Тестовый сервак",
"description": "Песочница для ботов"
}description может быть null. Ошибки: 403 — приложение не установлено на этот канал.
GET /channels/{channelId}/rooms
Комнаты канала и их категории. Отсюда бот узнаёт, куда писать и куда заходить, — идентификаторы комнат больше не нужно брать руками из адресной строки.
bash
curl https://emotify.ru/api/v1/channels/$CHANNEL/rooms \
-H "Authorization: Bot $BOT_TOKEN"Ответ 200:
json
{
"rooms": [
{
"id": "a0d4cc29-…",
"name": "Приветственный",
"type": "text",
"categoryId": "b71f0a35-…",
"sort": 1,
"writeRestricted": false
},
{
"id": "29004975-…",
"name": "Гостинная",
"type": "voice",
"categoryId": null,
"sort": 2,
"writeRestricted": false,
"userLimit": 10,
"users": [
{ "id": "b49c3095-…", "name": "Z3oM", "channelNickname": "Хозяин" }
]
}
],
"categories": [
{ "id": "b71f0a35-…", "name": "Общение", "sort": 1 }
]
}| Поле комнаты | Описание |
|---|---|
type | text — текстовая · voice — голосовая · stream — чат трансляции |
categoryId | категория из categories, либо null |
sort | порядок внутри канала, как в портале |
writeRestricted | писать в комнату могут не все — у роли бота может не быть права |
userLimit | только у голосовых: предел участников, null — без предела |
users | только у голосовых: кто сейчас в комнате; пустой массив — никого |
Приватные комнаты не отдаются
Их нет в ответе, даже если бот в них допущен. Состав приватных голосовых тоже недоступен.
Ошибки: 403 — приложение не установлено на канал.
Типичное применение — найти комнату по имени и написать в неё:
js
const { rooms } = await api(`/channels/${channelId}/rooms`).then((r) => r.json())
const general = rooms.find((room) => room.type === 'text' && room.name === 'Общий')
if (general) {
await api(`/rooms/${general.id}/messages`, {
method: 'POST',
body: JSON.stringify({ channelId, text: 'Бот на связи' })
})
}Или узнать, есть ли кто-нибудь в голосовых:
js
const busy = rooms.filter((room) => room.type === 'voice' && room.users.length > 0)
console.log(busy.map((room) => `${room.name}: ${room.users.length}`))Сообщения
POST /rooms/{roomId}/messages — отправить
| Параметр | Где | Тип | Обязателен | Описание |
|---|---|---|---|---|
roomId | путь | uuid | да | текстовая комната |
channelId | тело | uuid | да | канал, которому принадлежит комната |
text | тело | string | да | текст, 1–4000 символов |
replyToId | тело | uuid | нет | сообщение, на которое отвечаем |
bash
curl -X POST https://emotify.ru/api/v1/rooms/$ROOM/messages \
-H "Authorization: Bot $BOT_TOKEN" -H "Content-Type: application/json" \
-d "{\"channelId\":\"$CHANNEL\",\"text\":\"Привет!\"}"js
await fetch(`https://emotify.ru/api/v1/rooms/${roomId}/messages`, {
method: 'POST',
headers: { Authorization: `Bot ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ channelId, text: 'Привет!' })
})Ответ 201:
json
{
"id": "3f8a1c62-…",
"text": "Привет!",
"createdAt": "2026-09-19T12:00:00.000Z",
"author": { "id": "a2d78dd9-…", "name": "Пример бота" }
}Ошибки: 403 — нет права писать в комнаты · 422 — комната не текстовая или не принадлежит channelId · 429 — больше 20 сообщений в минуту.
В тексте работает разметка Markdown — так же, как у людей: **жирный**, *курсив*, ``код``, ссылки.
GET /rooms/{roomId}/messages — история
| Параметр | Где | Тип | Обязателен | Описание |
|---|---|---|---|---|
roomId | путь | uuid | да | текстовая комната |
channelId | query | uuid | да | канал комнаты |
limit | query | число | нет | 1–100 |
beforeId | query | uuid | нет | отдать сообщения старее этого — для листания |
bash
curl "https://emotify.ru/api/v1/rooms/$ROOM/messages?channelId=$CHANNEL&limit=50" \
-H "Authorization: Bot $BOT_TOKEN"Ответ 200:
json
{
"messages": [
{
"id": "3f8a1c62-…",
"text": "Привет!",
"createdAt": "2026-09-19T12:00:00.000Z",
"author": { "id": "…", "name": "Z3oM" }
}
],
"hasMore": true
}Сообщения идут от новых к старым. Чтобы получить следующую страницу, передайте beforeId — id последнего сообщения из предыдущего ответа. Удалённые сообщения в выдачу не попадают.
Ошибки: 403 — нет права читать историю.
PATCH /rooms/{roomId}/messages/{messageId} — изменить своё сообщение
| Параметр | Где | Тип | Обязателен | Описание |
|---|---|---|---|---|
messageId | путь | uuid | да | сообщение, написанное ботом |
channelId | тело | uuid | да | канал комнаты |
text | тело | string | да | новый текст, 1–4000 символов |
bash
curl -X PATCH https://emotify.ru/api/v1/rooms/$ROOM/messages/$MSG \
-H "Authorization: Bot $BOT_TOKEN" -H "Content-Type: application/json" \
-d "{\"channelId\":\"$CHANNEL\",\"text\":\"Сборка завершена ✅\"}"Ответ 200:
json
{
"id": "3f8a1c62-…",
"text": "Сборка завершена ✅",
"editedAt": "2026-09-19T12:08:22.521Z"
}Ошибки: 403 — сообщение чужое, удалено, не найдено или у роли нет права edit_text_msg_you_own. Эти случаи снаружи неразличимы, поэтому ответ один на все.
Одно сообщение вместо десяти
Правка удобна для всего, что меняется: прогресс задачи, счёт в игре, статус деплоя. Бот пишет сообщение один раз и обновляет его, не засоряя комнату.
Править можно 30 минут
После этого правка своего сообщения перестаёт проходить — ограничение общее для людей и ботов.
DELETE /rooms/{roomId}/messages/{messageId} — удалить своё сообщение
| Параметр | Где | Тип | Обязателен |
|---|---|---|---|
messageId | путь | uuid | да |
channelId | тело | uuid | да |
bash
curl -X DELETE https://emotify.ru/api/v1/rooms/$ROOM/messages/$MSG \
-H "Authorization: Bot $BOT_TOKEN" -H "Content-Type: application/json" \
-d "{\"channelId\":\"$CHANNEL\"}"Ответ 204 без тела. 403 — сообщение чужое или нет права delete_text_msg_you_own.
Удаление тратит квоту записи (20 в минуту), как и отправка.
POST /rooms/{roomId}/messages/{messageId}/reactions — поставить реакцию
| Параметр | Где | Тип | Обязателен | Описание |
|---|---|---|---|---|
messageId | путь | uuid | да | сообщение |
channelId | тело | uuid | да | канал |
emoji | тело | string | да | эмодзи, например "👍" |
bash
curl -X POST https://emotify.ru/api/v1/rooms/$ROOM/messages/$MSG/reactions \
-H "Authorization: Bot $BOT_TOKEN" -H "Content-Type: application/json" \
-d "{\"channelId\":\"$CHANNEL\",\"emoji\":\"👍\"}"Ответ 201: { "emoji": "👍", "messageId": "…" }
DELETE /rooms/{roomId}/messages/{messageId}/reactions — снять реакцию
Те же параметры. Снимает реакцию, поставленную ботом. Ответ 204 без тела.
Участники
GET /channels/{channelId}/members
Состав канала: люди и боты.
bash
curl https://emotify.ru/api/v1/channels/$CHANNEL/members \
-H "Authorization: Bot $BOT_TOKEN"Ответ 200:
json
{
"members": [
{
"id": "b49c3095-…",
"name": "Z3oM",
"channelNickname": "Хозяин",
"online": true,
"isBot": false,
"avatar": "412d5d2b-….jpg",
"avatarVersion": 3,
"roles": [{ "systemName": "CHANNEL_OWNER", "name": "Владелец канала" }]
}
],
"online": 12,
"total": 294,
"hasMore": true
}| Поле | Описание |
|---|---|
members | участники: все онлайн плюс часть офлайновых |
online | сколько участников канала сейчас в сети |
total | сколько участников на канале всего |
hasMore | есть ли ещё офлайновые участники за пределами выдачи |
Если нужен только счётчик, читайте online и total — перебирать members не нужно: на больших каналах офлайновая часть приходит не целиком.
Боты тоже участники
isBot: true — это другое приложение на том же канале. Боты входят в total и в online, так что «сколько здесь людей» считается фильтром по isBot — см. рецепт.
channelNickname — ник участника на этом канале (может быть null); name — его общее имя в Emotify. Показывайте ник, если он есть, иначе имя — так делает и сам портал.
Голос
POST /rooms/{roomId}/voice — войти в голосовую
| Параметр | Где | Тип | Обязателен | Описание |
|---|---|---|---|---|
roomId | путь | uuid | да | голосовая комната |
channelId | тело | uuid | да | канал комнаты |
bash
curl -X POST https://emotify.ru/api/v1/rooms/$VOICE_ROOM/voice \
-H "Authorization: Bot $BOT_TOKEN" -H "Content-Type: application/json" \
-d "{\"channelId\":\"$CHANNEL\"}"Ответ 200:
json
{
"roomId": "29004975-…",
"token": "eyJhbGciOi…",
"url": "https://emotify.ru/ws/",
"canPublish": true,
"identity": "a2d78dd9-…"
}После этого запроса бот уже виден в комнате. Чтобы он был и слышен, подключитесь к LiveKit полученным токеном и публикуйте звук, например через @livekit/rtc-node:
js
import { Room, AudioSource, LocalAudioTrack, TrackPublishOptions } from '@livekit/rtc-node'
const { token, url } = await joinVoice(roomId, channelId) // запрос выше
const room = new Room()
await room.connect(url, token)
const source = new AudioSource(48000, 1)
const track = LocalAudioTrack.createAudioTrack('sound', source)
await room.localParticipant.publishTrack(track, new TrackPublishOptions())
// дальше пишите PCM-кадры в sourcecanPublish: false означает, что у роли бота нет права говорить — публикация не заработает, пока администратор не выдаст право talk_voice_room.
Ошибки: 422 — комната не голосовая или не принадлежит каналу.
DELETE /rooms/{roomId}/voice — выйти
Без тела. Ответ 204. Бот исчезает из комнаты.
Команды
Список того, что умеет бот. Портал показывает его людям в трёх местах: кнопкой у поля ввода, подсказкой при наборе / и на экране установки приложения на канал.
Команда — это обычное сообщение, начинающееся с /. Портал его не разбирает и никуда не маршрутизирует: событие о сообщении по-прежнему приходит всем ботам канала, и разбирать текст должен сам бот. Если два бота объявили /погода, ответят оба — в списке каждая команда показана вместе с именем своего бота.
Требуется scope commands — см. ключ и его scope.
Реагируете на команды — обязаны их объявить
Это требование платформы, а не пожелание. Незарегистрированной команды для портала не существует: узнать о ней можно только от вас лично.
Люди не читают документацию к боту — они нажимают кнопку у поля ввода и набирают /. Бот, который молча ждёт !погода, для участника канала неотличим от сломанного: он ставится, висит в списке участников и ничего не делает.
Объявляйте список при каждом запуске программы и держите его рядом с обработчиками: тогда объявленное и обрабатываемое не разъезжаются.
Если команд нет
Объявлять нечего — это нормальный случай, и портал с ним работает честно:
| Бот | Что делает |
|---|---|
| Бот-уведомитель (только пишет: CI, мониторинг, заказы) | команд у него нет по природе — не объявляйте ничего |
| Голосовой бот без текстовых команд | то же самое |
| Бот, реагирующий на обычную речь (эхо, автоответчик) | команд нет, но опишите поведение администратору при установке |
Что видят люди, когда команд нет:
- кнопки команд у поля ввода не появляется вовсе — не серой, не пустой, её просто нет. Кнопка, открывающая пустоту, хуже отсутствующей;
- набор
/ничего не подсказывает — сообщение отправляется как обычный текст; - на экране установки приложения и в кабинете канала блок команд не показывается.
⚠️ Всё это видно на уровне канала: если на канале стоят два бота и команды объявил только один, кнопка есть, и в ней перечислены команды этого одного.
Пустой массив стирает объявленное — бот исчезает из подсказок, оставаясь на канале:
bash
curl -X PUT https://emotify.ru/api/v1/commands -H "Authorization: Bot $BOT_TOKEN" -H "Content-Type: application/json" -d '{"commands":[]}'PUT /commands — объявить команды
Заменяет список целиком: что прислали, то и стало. Отдельного «добавь одну» нет намеренно — иначе команда, удалённая из кода бота, осталась бы висеть в меню навсегда.
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
commands | array | да | до 25 команд; порядок в массиве = порядок показа |
commands[].name | string | да | начинается с /, дальше буквы (латиница или кириллица), цифры и дефис, до 32 символов |
commands[].description | string | да | что делает команда, до 120 символов |
commands[].usage | string | нет | подсказка аргумента, например <город>, до 60 символов |
bash
curl -X PUT https://emotify.ru/api/v1/commands \
-H "Authorization: Bot $BOT_TOKEN" -H "Content-Type: application/json" \
-d '{
"commands": [
{ "name": "/погода", "description": "Погода в городе", "usage": "<город>" },
{ "name": "/помощь", "description": "Что я умею" }
]
}'js
await fetch('https://emotify.ru/api/v1/commands', {
method: 'PUT',
headers: {
Authorization: `Bot ${process.env.BOT_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
commands: [
{ name: '/погода', description: 'Погода в городе', usage: '<город>' },
{ name: '/помощь', description: 'Что я умею' }
]
})
})Ответ 200:
json
{
"commands": [
{
"name": "/погода",
"description": "Погода в городе",
"usage": "<город>",
"position": 1
},
{ "name": "/помощь", "description": "Что я умею", "usage": null, "position": 2 }
]
}position присваивает сервер по порядку массива — присылать его не нужно.
Пустой массив стирает все команды: бот перестаёт показываться в подсказках.
| Код | Когда |
|---|---|
400 | имя без / или с недопустимыми символами, описание пустое или длиннее 120, больше 25 команд, одна и та же команда дважды |
403 | у ключа нет scope commands — перевыпустите ключ в кабинете приложения |
429 | превышена квота записи |
GET /commands — что объявлено сейчас
bash
curl https://emotify.ru/api/v1/commands -H "Authorization: Bot $BOT_TOKEN"Ответ 200 — тот же формат, что у PUT.
Команды принадлежат приложению, а не каналу: объявляются один раз и действуют на всех каналах, где бот установлен.
Объявляйте при старте
Держите список рядом с кодом обработчиков и отправляйте его при запуске бота — тогда объявленное и обрабатываемое не разъезжаются.
События: токен подписки
GET /events/token
Токен для получения событий по WebSocket — как им пользоваться, описано на странице события.
Ответ 200:
json
{
"token": "eyJhbGciOi…",
"channel": "bot:16c6e6b1-…",
"url": "wss://emotify.ru/cf/connection/websocket",
"expiresIn": 21600
}Токен живёт 6 часов. Запрашивайте новый заранее — этим же маршрутом.
Вебхук
Управление адресом доставки событий. Формат доставок и проверка подписи — на странице события.
PUT /webhook — задать адрес
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
url | string | да | публичный https-адрес, до 500 символов |
rotateSecret | boolean | нет | true — выпустить новый ключ подписи |
bash
curl -X PUT https://emotify.ru/api/v1/webhook \
-H "Authorization: Bot $BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"url":"https://bot.example.com/emotify"}'Ответ 201 (при создании) или 200 (при обновлении):
json
{
"url": "https://bot.example.com/emotify",
"active": true,
"secret": "whsec_…",
"signatureHeader": "X-Emotify-Signature"
}secret приходит только при создании и при rotateSecret: true — сохраните его.
localhost и адреса частных сетей не принимаются: нужен публичный хост. Для локальной отладки используйте туннель (ngrok и подобные) или WebSocket-транспорт.
GET /webhook — состояние
Ответ 200:
json
{
"url": "https://bot.example.com/emotify",
"active": true,
"failedStreak": 0,
"createdAt": "2026-09-18T10:00:00.000Z"
}active: false с ненулевым failedStreak означает, что доставки отключены после серии неудач — почините приёмник и задайте адрес заново через PUT.
404 — вебхук не настроен.
DELETE /webhook — удалить
Ответ 204. События перестают уходить.
Маршруты с токеном пользователя
Работают с Authorization: Bearer <токен>, полученным по входу через Emotify. Ключ приложения здесь не подходит.
GET /users/@me
Требует scope emotify.identify.
json
{
"id": "b3f4d17e-…",
"name": "KatrinKa 1",
"avatar": "412d5d2b-….jpg",
"avatarVersion": 10,
"scopes": ["openid", "emotify.identify"]
}GET /users/@me/channels
Требует scope emotify.channels.read.
json
{
"channels": [
{
"id": "4facb875-…",
"name": "Тестовый сервак",
"image": "channel-icon.jpg",
"avatarVersion": 2,
"role": { "systemName": "CHANNEL_OWNER", "name": "Владелец канала" }
}
]
}