Быстрый старт
User API делает от вашего имени всё, что вы делаете в приложении VZ Chat: пишет сообщения,
заводит чаты и группы, меняет профиль. Адрес API — https://api.vz-chat.com/v1.
- Войдите в кабинет по номеру телефона и выпустите ключ.
- Передавайте его в каждом запросе:
Authorization: Bearer vzc_… - Напишите себе в «Избранное»:
CHAT=$(curl -s https://api.vz-chat.com/v1/chats/saved -H "Authorization: Bearer $KEY" | jq -r .data.id)
curl -X POST https://api.vz-chat.com/v1/chats/$CHAT/messages \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Привет из API"}'
Ключи и права
Ключ начинается с vzc_ и показывается один раз — при выпуске.
У нас хранится только его хеш: восстановить ключ нельзя, можно отозвать и выпустить новый.
Заводите отдельный ключ на каждую интеграцию — тогда любую можно отключить, не трогая остальные.
| Право | Что даёт |
|---|---|
| read | Читать чаты, сообщения, людей, события; скачивать вложения |
| write | Писать и удалять сообщения, заводить и менять чаты, отмечать прочитанное |
| profile | Менять свой профиль и фотографию, записную книжку |
| webhooks | Заводить и настраивать вебхуки |
Прав у ключа не больше, чем у вас самих: чужие чаты он не видит, чужую группу не переименует.
Ответы, ошибки, лимиты
Всё — JSON, поля в snake_case, время — ISO 8601 с поясом.
Объект приходит в data, список — массивом в data.
Идентификаторы — ULID: строки, которые растут со временем, поэтому их можно сравнивать.
Ошибки
HTTP/1.1 422 Unprocessable Content
{
"message": "Поле Текст обязательно для заполнения.",
"errors": { "text": ["Поле Текст обязательно для заполнения."] }
}
| 401 | Ключа нет, он отозван или истёк |
| 403 | У ключа нет нужного права — или действие запрещено вам самим |
| 404 | Не найдено. Чужой чат тоже «не найден»: по ответу нельзя узнать, что он есть |
| 422 | Ошибка в данных; подробности в errors |
| 429 | Превышен лимит; подождите столько секунд, сколько в Retry-After |
Лимиты
300 запросов в минуту на ключ, из них не больше 60 отправок сообщений.
Профиль
GET /me · read
Ваш профиль — с телефоном и датой рождения, которых другим не видно.
PATCH /me · profile
Передайте только то, что меняете: first_name, last_name,
bio (до 200 знаков), birthday (ГГГГ-ММ-ДД),
nickname (латиница, цифры, «_», 5–32 знака).
curl -X PATCH https://api.vz-chat.com/v1/me -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" -d '{"bio": "Строю дома"}'
POST /me/avatar · profile
Фотография JPEG, PNG или WebP до 8 МБ — файлом (file), ссылкой (url) или base64.
DELETE /me/avatar убирает её.
Чаты
GET /chats · read
Все ваши чаты, свежие сверху, с последним сообщением и непрочитанным.
GET /chats/saved · read
«Избранное» — чат с самим собой. Есть у каждого.
GET /chats/{id} · read
POST /chats · write
Личка с человеком — второй раз не заводится, вернётся существующая. Себе — это «Избранное».
{"type": "direct", "user_id": "01k…"}
Группа: вы — владелец, остальные — участники.
{
"type": "group",
"title": "Стройка на Кленовой",
"member_ids": ["01k…", "01k…"],
"avatar": {"url": "https://example.com/house.jpg"}
}
PATCH /chats/{id} · write
Только группа: title, description,
avatar (url или base64), remove_avatar.
POST /chats/{id}/read · write
Отмечает прочитанным всё до message_id включительно; без него — всё. Собеседник увидит две синие галочки.
DELETE /chats/{id} · write
У себя; с {"for_everyone": true} — у всех (группу так удаляет только владелец). «Избранное» не удаляется, а очищается.
Участники группы
Управляют составом владелец и админы, назначать админов может только владелец.
GET /chats/{id}/members · read
POST /chats/{id}/members · write
{"user_ids": ["01k…", "01k…"]}
DELETE /chats/{id}/members/{user_id} · write
PUT /chats/{id}/members/{user_id}/role · write
{"role": "admin"} // или "member"
Сообщения
GET /chats/{id}/messages · read
Лента по времени. Без параметров — последние 50. Листайте курсором:
before=<id> — старше, after=<id> — новее,
limit — до 100. has_more скажет, есть ли ещё.
curl "https://api.vz-chat.com/v1/chats/$CHAT/messages?before=01k…&limit=100" -H "Authorization: Bearer $KEY"
GET /chats/{id}/messages/{message_id} · read
POST /chats/{id}/messages · write
Текст, ответ, вложения, стикер или голосовое. Нужно хотя бы что-то одно.
{
"text": "Смета во вложении",
"reply_to_id": "01k…",
"client_id": "7b3f1c2e-…",
"attachments": [
{"url": "https://example.com/smeta.pdf"},
{"base64": "iVBORw0KGgo…", "name": "план.png"},
{"url": "https://example.com/photo.jpg", "as_file": true}
]
}
client_id— ваш UUID против дублей: повтор с тем же ключом вернёт то же сообщение, а не второе. Ставьте его всегда, когда запрос могут повторить.as_file— картинку прислать документом, без сжатия.sticker— идентификатор стикера вместо текста.voice— голосовое:{"url"|"base64": …, "duration_ms": 3200, "waveform": [0…100]}, звук m4a/aac/mp3.
DELETE /chats/{id}/messages/{message_id} · write
У себя; с {"for_everyone": true} — у всех, но только своё.
Файлы
Любой файл — вложение, аватар — можно передать тремя способами:
- Загрузкой (multipart):
attachments[0][file]=@smeta.pdf. - Ссылкой:
{"url": "https://…"}— сервер скачает файл сам. Только http и https на публичные адреса, без перенаправлений; ждём до 30 секунд. - Строкой base64:
{"base64": "…", "name": "план.png"}; можно с приставкойdata:image/png;base64,.
До 10 файлов в сообщении, каждый до 10 МБ. Тип определяем по содержимому, а не по имени.
Не прошёл один файл — ошибка придёт с его номером: attachments.2.
curl -X POST https://api.vz-chat.com/v1/chats/$CHAT/messages -H "Authorization: Bearer $KEY" \ -F "text=Фото с объекта" -F "attachments[0][file]=@photo.jpg"
Скачать вложение
preview_url открыт всем и годится для показа. Оригинал —
по download_url с ключом: API проверит, что вы в этом чате,
и перенаправит на ссылку, которая живёт 15 минут.
curl -L -H "Authorization: Bearer $KEY" -o smeta.pdf "https://api.vz-chat.com/v1/attachments/01k…"
Люди и контакты
GET /people?q= · read
Поиск по имени, никнейму или номеру, от двух знаков.
GET /people/{id} · read
«В сети» и время последнего визита видно только тем, у кого с человеком есть общий чат.
GET /contacts · profile
POST /contacts · profile
{"user_id": "01k…", "name": "Прораб Игорь", "note": "Кленовая, 12"}
PUT /contacts · profile
Выгрузка книжки целиком: {"contacts": [{"phone": "+7…", "name": "…"}]}. Заменяет прежнюю, кроме добавленных вручную.
Объекты
Сообщение
{
"id": "01k8…",
"chat_id": "01k7…",
"type": "text", // text, image, file, sticker, voice, system
"text": "Смета во вложении",
"sticker": null,
"from": {"id": "01k5…", "display_name": "Марина Соколова"},
"reply_to_id": null,
"client_id": "7b3f1c2e-…",
"attachments": [{
"id": "01k8…", "kind": "file", "name": "smeta.pdf", "mime": "application/pdf",
"size": 182044, "width": null, "height": null, "duration_ms": null, "waveform": null,
"preview_url": null, "download_url": "https://api.vz-chat.com/v1/attachments/01k8…"
}],
"is_out": true,
"status": "read", // sent, delivered, read — только у своих
"created_at": "2026-09-30T12:04:11+00:00",
"edited_at": null
}
Чат
{
"id": "01k7…",
"type": "direct", // direct, group, saved
"title": "Марина Соколова", // у лички — имя собеседника, как оно записано у вас
"description": null,
"avatar_url": "https://…",
"companion": { /* человек — только у лички */ },
"members_count": 2,
"my_role": "member", // owner, admin, member
"unread_count": 3,
"notifications_muted": false,
"last_message": { /* сообщение */ },
"last_message_at": "2026-09-30T12:04:11+00:00",
"created_at": "2026-09-01T08:00:00+00:00"
}
Человек
{
"id": "01k5…",
"display_name": "Марина Соколова", // как записан у вас в книжке
"first_name": "Марина",
"last_name": "Соколова",
"nickname": "vz_a7k2m9",
"avatar_url": null,
"bio": null,
"is_staff": true,
"is_bot": false,
"is_online": false, // null, если общего чата нет
"last_seen_at": "2026-09-30T11:58:00+00:00"
}
События
Всё, что происходит в ваших чатах, пишется в журнал событий. Забирать его можно опросом или получать вебхуком — это один и тот же журнал, в одном порядке. События хранятся 7 дней.
{
"update_id": "01k8…", // растёт со временем — по нему offset
"type": "message.new",
"created_at": "2026-09-30T12:04:11+00:00",
"data": { … }
}
| type | Когда | data |
|---|---|---|
| message.new | Новое сообщение — входящее или ваше, отправленное с другого устройства | {message} |
| message.status | Участник получил или прочитал сообщения. Всё, что не новее отметки, — доставлено или прочитано | {chat_id, user_id, delivered_up_to, read_up_to} |
| message.deleted | Сообщение удалили у всех | {chat_id, message_id} |
| chat.created | Завели личку или группу, вас позвали в группу | {reason, chat} |
| chat.updated | Название, фото, состав, роли; очистка «Избранного» | {reason, chat} |
| chat.deleted | Чат удалён или вас убрали из группы | {chat_id, reason} |
Статус своего сообщения по message.status: если
read_up_to ≥ id — прочитано, если delivered_up_to ≥ id — доставлено.
id сравниваются как строки. В группе событие приходит от каждого участника.
Опрос
GET /updates · read
Как getUpdates в Telegram. Передайте offset —
next_offset из прошлого ответа, и придёт только новое.
С timeout (до 25 секунд) запрос подождёт, пока что-нибудь не случится.
types[] — только нужные события, limit — до 100.
offset="" while true; do r=$(curl -s "https://api.vz-chat.com/v1/updates?timeout=25&offset=$offset" -H "Authorization: Bearer $KEY") echo "$r" | jq -c '.data[]' offset=$(echo "$r" | jq -r '.next_offset // empty') done
Сохраняйте next_offset у себя: после перезапуска продолжите с того же места и ничего не потеряете, если уложитесь в 7 дней.
Вебхуки
Заведите вебхук в кабинете или через API — и события будут приходить POST-запросом на ваш адрес. До 10 вебхуков, у каждого свой набор событий.
Запрос
POST /your/hook HTTP/1.1
Content-Type: application/json
User-Agent: VZChat-Webhooks/1.0
X-VZ-Event: message.new
X-VZ-Update-Id: 01k8…
X-VZ-Timestamp: 1790000000
X-VZ-Signature: sha256=5d41402abc4b2a76b9719d911017c592…
{"update_id": "01k8…", "type": "message.new", "created_at": "…", "data": {…}}
Проверка подписи
Подпись — HMAC-SHA256 от строки «timestamp.тело» секретом вебхука.
Проверяйте её на каждом запросе и отбрасывайте те, у которых время старше пяти минут, — так
перехваченный запрос нельзя повторить. Считайте подпись от сырого тела, до разбора JSON.
// PHP
$body = file_get_contents('php://input');
$timestamp = (int) $_SERVER['HTTP_X_VZ_TIMESTAMP'];
$expected = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$body, $secret);
if (! hash_equals($expected, $_SERVER['HTTP_X_VZ_SIGNATURE'] ?? '') || abs(time() - $timestamp) > 300) {
http_response_code(401);
exit;
}
// Node.js
const crypto = require('crypto');
function verify(rawBody, headers, secret) {
const timestamp = headers['x-vz-timestamp'];
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`).digest('hex');
const given = headers['x-vz-signature'] || '';
return given.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))
&& Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
}
# Python
import hashlib, hmac, time
def verify(raw_body: bytes, headers, secret: str) -> bool:
timestamp = headers["X-VZ-Timestamp"]
expected = "sha256=" + hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, headers.get("X-VZ-Signature", "")) and abs(time.time() - int(timestamp)) < 300
Ответ и повторы
- Отвечайте любым кодом 2xx в течение 10 секунд. Тяжёлую работу делайте после ответа — в очереди.
- Не дошло — повторим через 10 секунд, минуту, 5, 15 минут и час.
- Одно событие может прийти дважды — сверяйте
update_id. - Порядок доставки не гарантирован: сортируйте по
update_id. - После 50 неудач подряд вебхук выключается. Починили приёмник — включите его в кабинете, а пропущенное заберите опросом.
- Адрес — только публичный http(s), перенаправлениям не следуем.
Управление через API · webhooks
| GET /webhooks | Список |
| POST /webhooks | {"url": "…", "events": ["message.new"]} — ответ с secret |
| GET /webhooks/{id} | Вебхук с секретом |
| PATCH /webhooks/{id} | url, events, is_active, rotate_secret |
| DELETE /webhooks/{id} | Удалить |
| POST /webhooks/{id}/test | Проверочная доставка события webhook.test |
| GET /webhooks/{id}/deliveries | Последние 100 доставок: код ответа, ошибка, время |