Один POST — и пост уходит сразу во все ваши каналы: телеграм, ВКонтакте, MAX, X. Очередь, повторы и ошибки площадок берём на себя; вы шлёте JSON из крона, бэкенда или скрипта на ноутбуке.
Нужен аккаунт с подключёнными каналами · 7 дней доступа за 10 ₽
Первый пост за три шага
1
Возьмите ключ
Ключи доступа → «Создать ключ». Он показывается один раз и начинается с smk_ — скопируйте сразу. У нас в базе лежит только его хеш, восстановить строку нельзя: потеряли — удалите и выпустите новый.
2
Отправьте пост
Подставьте свой ключ и ярлык канала. Ярлыки — то, чем каналы адресуются: посмотреть их можно запросом GET /api/v1/channelsили в кабинете.
Подключение каналов, оплата, выдача ключей и токены площадок наружу не выставлены намеренно: ключ даёт право вести каналы, а не управлять аккаунтом.
GET/api/v1/channels
Куда можно постить
С этого начинает любой клиент: id, площадка, имя канала и ярлыки. Токенов площадок и настроек подключения ручка не отдаёт вовсе. Параметр ?all=1 добавит выключенные каналы — чтобы понять, почему канал перестал получать посты.
Один вызов — один замысел сразу в несколько каналов. Куда: ярлыком (tags), площадкой целиком (providers) или списком id (channelIds); нужно хотя бы одно. Пост встаёт в ту же очередь, что и написанный руками в кабинете, и его разносит тот же отправщик.
Тело запроса целиком
{
"tags": ["новости"], // или "providers": ["telegram"], или "channelIds": [17, 18]
"content": "текст на все площадки",
// либо по площадкам: {"default": "…", "telegram": "<b>…</b>", "vk": "…", "max": "…", "x": "…"}
"format": "html", // plain (по умолчанию) или html
"mediaUrls": ["https://site.ru/card.png"],
"media": [{"type": "image", "rel": "…"}], // ответ /api/v1/media, как есть
"buttons": [
{"text": "Записаться", "url": "https://site.ru/zapis/"}
],
"firstComment": "Подробности: …",
"partial": true, // отказ одного канала не валит остальные; отвергнутые придут в skipped
"publishAt": "2026-09-06T09:00:00+03:00", // нет → уходит сейчас
"state": "queued", // draft — положить в кабинет, но не отправлять
"source": "moj-kron", // пара source + externalKey = защита от повтора
"externalKey": "obzharka-2026-09-05",
"payload": {} // что угодно, ляжет рядом с событием источника
}
GET/api/v1/posts?group=…
Что стало с постом
Состояние по строке на канал: очередь, публикация, ошибка площадки, ссылка на опубликованное. Ровно то, что стоит дёргать через минуту после публикации, если вам важен результат, а не факт постановки.
Нужна, когда публичной ссылки на файл нет: карточка родилась в памяти скрипта, скриншот лежит на диске. У кого ссылка есть — шлёт её прямо в mediaUrls поста, тогда запрос всего один. Принимает multipart (поле file) или голое тело с Content-Type файла и именем в X-File-Name. Блок media из ответа кладётся в пост как есть; url в ответе — наш роут отдачи за авторизацией, публичного адреса у файла не появляется и площадке он не нужен: туда файл уходит содержимым.
Поле title в контракте есть, но сегодня оно ни на что не влияет: ни одна из четырёх подключаемых площадок отдельного заголовка у поста не различает. Всё решает текст.
Ради этого API и затевался
Крон перезапустился — пост не уйдёт дважды
Передайте пару source +externalKey: имя вашего процесса и ваш собственный идентификатор события — номер выплаты, id заказа, дату слота. Пара уникальна на всю базу, и повторный запрос с ней не отправляет ничего: возвращается200 с duplicate: trueи тем же groupId, что и в первый раз. Работает это навсегда, а не несколько минут: та же выплата не уедет второй раз и через неделю.
Клиенту от этого проще некуда: не нужно помнить, что уже отправлено, не нужна своя таблица «постил / не постил». Упал посреди прогона, перезапустился, прогнал заново — лишнего не будет.
Второй вызов с той же парой
# тот же source + externalKey во второй раз
HTTP/1.1 200 OK
{
"duplicate": true,
"groupId": "6f1c2f3a-8d21-4a77-9d0e-2b7c1f0a5e44",
"posts": [{ "id": 812, "channelId": 17, "state": "published" }]
}
Оба поля задаются только вместе: одно без другого — 400.source обрезается до 64 знаков,externalKey — до 255.
Ключа не передали — защита всё равно есть, но другая и короткая: одинаковый запрос в те же каналы в пределах пяти минут считается повтором обрыва и второго поста не создаёт. Дальше пяти минут тот же текст уедет ещё раз — это уже осознанный повтор, и запрещать его мы не вправе. Крону нужна вечность, поэтому крону нужен ключ.
Что доедет до каждой площадки
Один замысел — четыре разных набора правил. Всё проверяемое мы проверяем при постановке в очередь, а не в момент отправки: узнать, что текст на триста знаков длиннее лимита, надо ДО того, как половина каналов уже опубликовалась.
Что
Телеграм
ВКонтакте
MAX
X
Длина текста
4096 знаков
4096 знаков
4000 знаков
280 знаков
С вложением
1024 в подписи — дальше текст уходит вторым сообщением, пост не отбивается
те же 4096 — вложение лимит не режет
те же 4000
те же 280
Вложения
до 10 картинок, видео и файлов (альбом); файл нельзя смешивать с картинкой
до 10 картинок; видео и файлы пока не грузим
до 12 вложений на сообщение, картинки и видео; файлы пока не грузим
до 4 картинок; видео пока не грузим
Разметка HTML
да
нет — теги снимаются, уходит чистый текст
да
нет — теги снимаются
Кнопки-ссылки
до 8, по одной в ряд
нет
нет
нет
Второе сообщение
да
нет
нет
да — уходит ответом на свой же пост
Правка опубликованного
да, пока посту не больше двух суток
нет — только снять и опубликовать заново
нет
нет
Ссылка на пост
есть у публичного канала, у приватного — нет
есть всегда
может не быть
есть всегда
Разметку и кнопки, которых площадка не умеет, мы не отбиваем, а тихо не шлём: пост во ВКонтакте уедет без тегов, в MAX — без кнопки. Отбить публикацию из-за кнопки было бы хуже, чем опубликовать без неё.
Единственное исключение — кнопки на альбоме: телеграм не принимает клавиатуру к группе вложений вовсе, и такой пост мы отбиваем (400) вместо тихой потери кнопок, ради которых его и собирали.
Вложений в одном посте — не больше десяти, считая и media, и mediaUrls. Ссылки мы скачиваем сами, по разу на замысел, а не на канал.
На этом API живёт наш собственный крон
Внешний вход затевался не под витрину: в него ходит крон Инвестминта — соседнего нашего проекта. Объявили дивиденды, скрипт нарисовал карточку и положил пост в очередь; дальше он живёт как любой другой, тем же отправщиком, с теми же повторами и ошибками.
Именно из-за него появилась пара source +externalKey: перезапуск тика без неё публиковал бы одну и ту же выплату дважды.
1 Крон увидел новую выплату
2 Собрал карточку и текст
3 Положил пост с externalKey вида SBER:2026:12M
4 Пост ушёл во все каналы с нужным ярлыком, одним замыслом
5 Тик повторился — вернулся duplicate, ничего не отправлено
API или MCP
Это не два продукта, а два входа в одну очередь. Ключ один и тот же, каналы те же, посты видны в одном календаре. Разница — кто принимает решение опубликовать.
Вы читаете эту страницу
HTTP API — когда постит программа
Крон, бэкенд, скрипт, кнопка в вашей админке
Защита от повтора по source + externalKey
Предсказуемые коды ответа и разбор ошибок в коде
Черновики: state: draft — положить, но не отправлять
«Нужен content…», «Укажите, куда постить…», «Под этот отбор не нашлось ни одного живого канала». Отдельный случай — «Пост не проходит по площадкам» со списком в issues: там по строке на канал, что именно длиннее лимита или несовместимо.
401
Ключа нет или он мёртв
Проверьте заголовок: Authorization: Bearer smk_… Удалённый в кабинете ключ перестаёт работать сразу, и «нет такого» от «отозван» снаружи не отличается намеренно.
402
Нет действующей подписки
Очередь закрыта: код no_subscription. Черновик (state: draft) при этом принимается — терять уже написанный текст из-за просроченной карты незачем.
403
Почта аккаунта не подтверждена
Тот же гейт, что и в кабинете. Ключ его не обходит — иначе он был бы дыркой в обход самой проверки.
404
Замысел не найден
GET по чужому или несуществующему groupId. Формат обязан быть uuid.
413
Файл больше предела
Картинка — до 10 МБ, видео и pdf — до 50 МБ.
429
Слишком часто
Больше 60 запросов в минуту на ключ. Отказ держится до конца минуты, повторять стоит с паузой.
Частые вопросы
Чем API отличается от MCP и что выбрать?
API — для кода: крон, бэкенд, скрипт. Вы сами формируете JSON, сами решаете, когда его отправить, и разбираете коды ответа. MCP — для разговора: вы говорите агенту «запости это в новости», решение принимает он. Ключ у обоих один и тот же, каналы и очередь общие, защита от повтора тоже есть у обоих. Если постит программа по расписанию — берите API; если человек словами — MCP.
Сколько это стоит и считаете ли вы публикации?
Публикации не считаем вовсе — ни через API, ни из кабинета. Тариф считается КАНАЛАМИ: сколько площадок и сообществ подключено, столько и платите. API входит в любой тариф отдельной строкой в счёте не идёт. Начать можно с 7 дней полного доступа за 10 ₽.
Что будет, если площадка откажет?
Проверяемое мы проверяем ДО записи: длина, вложения, разметка, кнопки — по всем выбранным каналам сразу. Не прошло по одной площадке — не создаём ничего и отвечаем 400 со списком в issues, чтобы половина каналов не оказалась опубликована. А вот отказ самой площадки в момент отправки — уже другое дело: пост встаёт в состояние error с текстом причины, остальные каналы этим не трогаются. Смотреть — GET /api/v1/posts?group=…
Можно ли получить ссылку на опубликованное?
Поле externalUrl в ответе на статус. У ВКонтакте и X она есть всегда. У телеграма — только если канал публичный: ссылка собирается из его ника и номера сообщения, а у приватного канала ника нет. У MAX ссылки может не быть вовсе. Пустой externalUrl не значит, что пост не вышел, — смотрите state и publishedAt.
Можно постить в чужие каналы?
Нет. Ключ видит ровно те каналы, которые подключены к вашему аккаунту, и постит только в них. Подключить канал через API нельзя — живые шаги там чужие: диплинк в телеграме, вход во ВКонтакте через браузер, добавление бота руками в MAX. Это делается в кабинете, человеком.
Что делать, если ключ утёк?
Удалить его в кабинете на странице ключей — одной кнопкой, и он перестаёт работать сразу; взамен выпускается новый. Всё, что успели опубликовать до этого, останется опубликованным, поэтому ключу не место в репозитории и в переписке. В базе у нас лежит только SHA-256, показать вам старый ключ мы не можем даже при желании.
Есть SDK или библиотека под ваш API?
Нет, и не планируем. Это обычный HTTP с JSON: четыре ручки, один заголовок авторизации. Библиотека здесь была бы прослойкой над curl — вы напишете интеграцию быстрее, чем прочитаете её README. Машиночитаемое описание для тех, кому оно нужно, лежит на /openapi.json.
Как поставить пост на конкретное время?
Полем publishAt в формате ISO 8601 и обязательно со смещением зоны: 2026-09-06T09:00:00+03:00 — это девять утра по Москве. Без смещения время читается как всемирное, и пост уйдёт на три часа раньше, чем вы думали. Время в прошлом ошибкой не считается — такой пост отправщик возьмёт первым же тиком.
Можно ли править и снимать посты через API?
Через HTTP-ручки — пока нет: снаружи выставлены постановка в очередь и статус. Править и снимать умеет MCP-канал того же ключа (smmly_edit_post, smmly_cancel_post) и, конечно, кабинет. Если вам это нужно из кода — напишите, куда и зачем: очередь на выставление уже есть.
Постите из своего кода
7 дней полного доступа за 10 ₽ — подключите каналы, возьмите ключ и отправьте первый пост тем же curl, что выше.