Разработчикам
Планируйте и публикуйте посты, управляйте каналами и медиафайлами, читайте уведомления и аналитику рабочего пространства followers.vc.
Базовый адрес: https://followers.vc/api/v1
Версия: 1.0.0
OpenAPI 3.1: openapi.json
Публичный API followers.vc, версия 1. Каждый запрос содержит ключ рабочего пространства: Authorization: Bearer fvc_…. Ключи создаются и отзываются на странице API keys рабочего пространства (/app/api-keys); ключ показывается один раз. Ключ действует с правами своего автора в команде и только в тех областях доступа, которые у него есть (ключ без областей может всё). Запросы передаются в JSON (Content-Type: application/json), кроме загрузки файла — она идёт как multipart/form-data; время в ответах — unix-секунды.
Ошибки всегда имеют вид {"error": {"code": "…", "message": "…", "fields": {…}}} и приходят с HTTP-статусом; fields есть только у ошибок валидации. Все списки имеют общую обёртку с data, meta и links; ?page начинается с 1, ?per_page по умолчанию равен 25, максимум — 100.
API followers.vc позволяет из вашего кода читать каналы, загружать медиа, создавать посты,
проверять их и повторять публикацию. Эта глава проведёт вас от ключа до запланированного поста.
Все endpoint, поля и коды ошибок ниже описаны в контракте OpenAPI (openapi.json).
api_not_in_plan./app/api-keys.fvc_… и показывается один раз. Скопируйте его сразу: хранится только
его хеш. Потеряли — отзовите ключ и создайте новый.| Область | Разрешает |
|---|---|
channels:read |
GET /channels, GET /channels/{id} |
media:read |
GET /media, GET /media/{id} |
media:write |
POST /media, POST /media/from-url, DELETE /media/{id} |
posts:read |
GET /posts, GET /posts/{id} |
posts:write |
POST /posts, PATCH /posts/{id}, DELETE /posts/{id}, POST /posts/{id}/retry |
notifications:read |
GET /notifications, GET /notifications/unread-count |
notifications:write |
POST /notifications/{id}/read, POST /notifications/read-all |
analytics:read |
GET /analytics, GET /analytics/{id} |
Кроме области, ресурсу нужен соответствующий модуль в тарифе (и в роли автора ключа): каналы,
файлы, публикации и аналитика. Без модуля ответ — module_not_in_plan, без области —
insufficient_scope.
Базовый адрес — https://followers.vc/api/v1. Ключ передаётся в заголовке Authorization, но не
в строке запроса:
Authorization: Bearer <key>
Запросы с телом — JSON (Content-Type: application/json), кроме загрузки файла POST /media:
она идёт как multipart/form-data. Время в ответах REST — unix-секунды.
GET /me не требует областей. Он показывает команду, тариф, ваши лимиты и сам ключ:
curl https://followers.vc/api/v1/me \
-H "Authorization: Bearer fvc_YOUR_KEY"
{
"data": {
"team": { "id": "t-91ac0f", "name": "Acme" },
"plan": { "name": "Pro", "expires_at": 1793000000 },
"limits": {
"rate_limit": 60,
"posts": { "limit": 300, "used": 12, "left": 288 },
"max_channels": 10
},
"key": { "name": "n8n", "prefix": "fvc_a1B2", "scopes": null }
}
}
rate_limit — запросов в минуту для этого ключа, -1 — без ограничения. scopes: null — все
области. plan.expires_at равен null, если тариф не истекает.
Нужна область channels:read. Необязательный фильтр network сужает список.
curl "https://followers.vc/api/v1/channels?network=telegram" \
-H "Authorization: Bearer fvc_YOUR_KEY"
У канала есть id, network, category, name, username, avatar, status, reason и
capabilities (что канал умеет публиковать). status — active, paused или
needs_reconnect; для последнего reason объясняет причину, а посты канала ждут. id нужен,
чтобы создать пост.
Нужна область media:write. Адрес должен быть публичным http или https; адреса внутри
частных сетей отклоняются ошибкой 422 по полю url.
curl -X POST https://followers.vc/api/v1/media/from-url \
-H "Authorization: Bearer fvc_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/photo.png", "name": "launch.png"}'
{
"data": {
"id": "fil-5f1c0e2a9b",
"name": "launch.png",
"url": "https://followers.vc/storage/files/photo.png",
"type": "image",
"mime": "image/png",
"extension": "png",
"size": 48213,
"width": 1080,
"height": 1080,
"duration": null,
"media_status": "ready",
"media_error": null,
"created_at": 1793000000
}
}
media_status — pending, пока размер и длительность читаются в фоне, затем ready или
failed (причина в media_error). Чтобы загрузить локальный файл, отправьте POST /media как
multipart/form-data с полем file:
curl -X POST https://followers.vc/api/v1/media \
-H "Authorization: Bearer fvc_YOUR_KEY" \
-F "file=@./photo.png"
Загрузка может завершиться ошибкой storage_limit (хранилище тарифа заполнено) или
file_too_large.
Нужны область posts:write и модуль публикаций в тарифе. В теле запроса:
channels — массив id каналов, от 1 до 50; на каждый канал создаётся свой пост;type — text, link или media;caption — текст;link — адрес, для поста типа link;media — массив id медиа из /media по порядку, для поста типа media;options — параметры конкретной сети, в том виде, как их принимает редактор;publish_at — когда публиковать.publish_at — ISO 8601 (2026-11-05T10:30:00+03:00, …Z) или unix-секунды. Без смещения берётся
часовой пояс автора ключа. Время должно быть в будущем.
curl -X POST https://followers.vc/api/v1/posts \
-H "Authorization: Bearer fvc_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c9a3e-launch-post-1" \
-d '{
"channels": ["a8d3f0c2e1b94f7d"],
"type": "media",
"caption": "Hello from the API",
"media": ["fil-5f1c0e2a9b"],
"publish_at": "2026-11-05T10:30:00+03:00"
}'
Не передавайте publish_at. Пост считается назначенным на «сейчас» и уходит в очередь
публикации; внутри запроса он никогда не публикуется, поэтому в ответе будет статус вроде
scheduled, а не результат.
curl -X POST https://followers.vc/api/v1/posts \
-H "Authorization: Bearer fvc_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"channels": ["a8d3f0c2e1b94f7d"], "type": "text", "caption": "Hello from the API"}'
Ответ — 201 с одним постом на канал:
{
"data": [
{
"id": "kQ7x9a1b2c3d",
"channel": { "id": "a8d3f0c2e1b94f7d", "network": "telegram", "name": "Acme news" },
"type": "text",
"caption": "Hello from the API",
"link": null,
"media": [],
"options": {},
"status": "scheduled",
"publish_at": 1793000000,
"attempts": 0
}
]
}
(У поста есть также retry_at, result и url; они пусты, пока сообщать нечего.) Если хотя бы
один канал отказал, ничего не сохраняется, а отказ приходит в поле channels.<id> ошибки
validation_failed. Исчерпанная квота постов — 403 post_quota.
POST /posts принимает заголовок Idempotency-Key. Тот же ключ с тем же телом в течение 24 часов
вернёт первый ответ с заголовком Idempotent-Replayed: true и не создаст пост заново, поэтому
повторять запрос после сбоя сети безопасно. Тот же ключ с другим телом — 422 idempotency_key_reused; тот же ключ, пока первый запрос ещё выполняется, — 409 idempotency_key_in_use. Сохраняется только успешный ответ.
Нужна область posts:read.
curl https://followers.vc/api/v1/posts/kQ7x9a1b2c3d \
-H "Authorization: Bearer fvc_YOUR_KEY"
status — один из draft, scheduled, reconnect_wait (запланирован, но канал нужно сначала
переподключить), published, failed. У опубликованного поста url — адрес в сети. В result
лежит ответ последней попытки: message, error_class (transient, uncertain — пост мог
быть опубликован —, auth или permanent) и social_id. После временной ошибки retry_at
показывает, когда очередь попробует снова. GET /posts/{id} возвращает ещё и attempt_log — все
попытки от старых к новым с полями attempt, outcome, error_class, message и at.
Список постов — GET /posts с фильтрами status, channel, from и to (по времени
публикации, ISO 8601 или unix-секунды).
Нужна область posts:write. Повторить можно только пост со статусом failed; любой другой
статус даёт 409 not_retryable.
curl -X POST https://followers.vc/api/v1/posts/kQ7x9a1b2c3d/retry \
-H "Authorization: Bearer fvc_YOUR_KEY"
Ответ — пост в той же форме, что у GET /posts/{id}. Следите за результатом опросом или
подпишитесь на события post.published и post.failed (глава «Вебхуки»).
Все списки (/channels, /media, /posts, /notifications) имеют один конверт:
{
"data": [],
"meta": { "page": 1, "per_page": 25, "total": 40 },
"links": {
"next": "https://followers.vc/api/v1/posts?per_page=25&page=2",
"prev": null
}
}
?page начинается с 1. ?per_page по умолчанию 25, максимум 100. На последней странице
links.next равен null. Ссылки сохраняют per_page и действующие фильтры, так что переходите
по ним как есть.
У каждой ошибки одна форма и HTTP-статус:
{
"error": {
"code": "validation_failed",
"message": "…",
"fields": { "channels": ["The channels field is required."] }
}
}
fields есть только у ошибок валидации. Ветвитесь по code, а не по message.
| Статус | Коды |
|---|---|
| 401 | unauthenticated — ключа нет, он неизвестен или отозван |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope, storage_limit, file_too_large, post_quota, analytics_not_in_plan |
| 404 | not_found, analytics_unavailable |
| 405 | method_not_allowed |
| 409 | not_editable, not_deletable, not_retryable, idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | validation_failed, idempotency_key_reused, not_saved |
| 429 | rate_limited |
| 500 | server_error — подробности никогда не отправляются |
Полный каталог — x-error-codes в openapi.json.
Лимит считается на ключ в минуту и берётся из тарифа (по умолчанию 60; limits.rate_limit в
GET /me). В ответах есть:
X-RateLimit-Limit — запросов в минуту для этого ключа; отсутствует, если у тарифа нет лимита;X-RateLimit-Remaining — сколько запросов осталось в текущую минуту.При превышении API отвечает 429 с кодом rate_limited и заголовком Retry-After (сколько
секунд ждать). Подождите это время перед следующим запросом.
Приложение может действовать от имени человека в followers.vc без ключа API: оно просит доступ, человек разрешает его на экране согласия, и приложение получает токены для этого человека и команды, в которой он работает. Это OAuth 2.1 с кодом авторизации и PKCE.
| Что | Адрес |
|---|---|
| Экран согласия (адрес авторизации) | https://followers.vc/app/api-keys/authorize |
| Токен | POST https://followers.vc/oauth/token |
| Отзыв | POST https://followers.vc/oauth/revoke |
| Регистрация клиента им самим | POST https://followers.vc/oauth/register |
| Сведения о сервере | GET https://followers.vc/.well-known/oauth-authorization-server |
| Сведения о ресурсе | GET https://followers.vc/.well-known/oauth-protected-resource/api/v1 (и /api/mcp) |
В кабинете откройте Ключи API → OAuth-приложения и подключённые приложения и зарегистрируйте
приложение: название, которое люди увидят на экране согласия, и адреса возврата, по одному в строке.
Адрес возврата — https, или http на localhost и 127.0.0.1 для приложения, которое работает на
компьютере человека. Вы получите client_id и client_secret; секрет показывается один раз.
Приложение без сервера (CLI, программа для компьютера или телефона) регистрируется как публичное:
у него нет секрета, оно подтверждает себя только через PKCE.
code_verifier (43–128 символов) и его code_challenge =
BASE64URL(SHA256(code_verifier)).response_type=code, client_id, redirect_uri,
state, scope (через пробел; без него — все области), code_challenge и
code_challenge_method=S256. PKCE обязателен для любого приложения.code, state и iss или с error=access_denied.grant_type=authorization_code, code,
redirect_uri, code_verifier, client_id (и client_secret — в теле или через HTTP Basic).
Код действует один раз.access_token (fvc_at_…, на час), refresh_token (fvc_rt_…, на 30 дней),
expires_in и scope. Вызывайте API с Authorization: Bearer <access_token>, как с ключом.grant_type=refresh_token, refresh_token, client_id
(и секрет). Старая пара перестаёт работать. Токен обновления, использованный второй раз,
прекращает весь доступ приложения: нужно новое согласие.Ошибки этих адресов — в формате OAuth: {"error": "invalid_grant", "error_description": "…"}.
Области — те же, что у ключей API (см. «Быстрый старт»). Токен действует с областями, которые разрешил человек, и с его ролью в команде: у участника с ролью «Пользователь» нет доступа к API, поэтому экран согласия для него закрыт, как и ключи. Лимит запросов тарифа действует на каждый токен.
Приложение отзывает токен на адресе отзыва (token, client_id, секрет): токен доступа и токен
обновления этого согласия перестают работать вместе. Человек видит разрешённые им приложения в
разделе Подключённые приложения и может отозвать любое. Удаление зарегистрированного приложения
прекращает все его токены.
Клиенту сервера MCP (/api/mcp) регистрация не нужна. Без токена сервер отвечает 401 с
WWW-Authenticate: Bearer resource_metadata="…"; клиент читает сведения, регистрируется сам на
/oauth/register как публичный и открывает экран согласия. На экране согласия сказано, что такое
приложение зарегистрировалось само и сайтом не проверено.
Вместо регистрации клиент может взять в качестве client_id https-адрес собственного JSON-документа
(например, https://app.example/oauth/client.json; путь обязателен). Адреса авторизации и токена
загружают его сами: только публичные адреса, без перенаправлений, 5 секунд, не больше 16 КБ. Документ —
JSON-объект, где client_id равен его собственному адресу, client_name не пуст и redirect_uris —
непустой список (https или http на localhost); token_endpoint_auth_method может быть только none,
client_secret быть не должно. redirect_uri запроса обязан точно совпасть с одним из redirect_uris.
PKCE с S256 обязателен, как у любого клиента. Документ кэшируется на срок Cache-Control: max-age
(от 5 минут до 24 часов, по умолчанию час). Если он не загрузился или не прошёл проверку, экран согласия
показывает ошибку, а адрес токена отвечает invalid_client. Экран согласия показывает имя из документа и
хост адреса и сообщает, что сайт клиента не проверял. Динамическая регистрация на /oauth/register
работает по-прежнему. Сведения сервера сообщают об обоих способах: client_id_metadata_document_supported
и registration_endpoint.
Каждый возврат с адреса авторизации (код, access_denied, любая другая ошибка) содержит iss — издателя
ровно так, как он указан в метаданных (RFC 9207); в метаданных есть
authorization_response_iss_parameter_supported: true. Клиенту стоит сверять iss с издателем, с которого
он начал.
Если у токена нет области, которая нужна запросу, API отвечает 403 insufficient_scope и заголовком
WWW-Authenticate: Bearer error="insufficient_scope", scope="<область>", resource_metadata="…". Клиент
может снова отправить человека на экран согласия, запросив область из заголовка (вместе с уже
имеющимися). Вызов инструмента сервера MCP сообщает тот же отказ в результате инструмента, а не в HTTP.
У followers.vc есть MCP-сервер (Model Context Protocol): ИИ-ассистенты, например Claude и Cursor, могут просматривать ваши каналы, загружать медиа, создавать посты, проверять и повторять их. Работает с тем же ключом API, что и REST.
POST https://followers.vc/api/mcp.GET /api/mcp отвечает 405: потока сообщений от сервера нет.Authorization: Bearer <key>) и тот же лимит запросов, что у
REST API. О ключах и лимитах — в главе «Быстрый старт».Сервер понимает версии 2026-07-28, 2025-11-25, 2025-06-18 и 2025-03-26.
params._meta["io.modelcontextprotocol/protocolVersion"]
и в заголовке MCP-Protocol-Version, метод — в заголовке Mcp-Method, имя инструмента для tools/call — в Mcp-Name.
Заголовки должны совпадать с телом, иначе 400 и ошибка -32020. Методы: server/discover (версии, возможности,
инструкции), tools/list, tools/call; у каждого результата resultType: "complete" и сведения о сервере в
_meta["io.modelcontextprotocol/serverInfo"], у tools/list и server/discover — ttlMs и cacheScope.
Неизвестный метод — 404 и -32601.initialize выбирает версию, дальше ping, tools/list, tools/call.Версия, которую сервер не знает, — 400 и ошибка -32022 со списком поддерживаемых в error.data.supported.
Инструменты выполняют те же действия, что и маршруты REST, и проверяют то же самое: модуль в
тарифе (и в роли автора ключа) и область ключа. Ключ с областями channels:read, media:read,
media:write, posts:read, posts:write, notifications:read, notifications:write и
analytics:read может пользоваться всеми инструментами. Ключ с меньшим набором тоже работает:
недоступный ему инструмент отвечает ошибкой (см. «Ошибки»).
| Инструмент | Что делает | Область |
|---|---|---|
list_channels |
Список каналов команды: id (нужен для create_post), сеть, статус (active, paused, needs_reconnect с причиной) и что канал умеет публиковать. Необязательный network. |
channels:read |
upload_media_from_url |
Скачивает изображение или видео с публичного http(s)-адреса в медиатеку; возвращает id медиа. Принимает url (обязательный) и name. |
media:write |
create_post |
Создаёт пост на одном или нескольких каналах, по посту на канал. Принимает channels (обязательный, от 1 до 50 id), type (text, link, media; обязательный), caption (обязательный), link, media (id из upload_media_from_url, до 50) и publish_at. Без publish_at пост ставится в очередь на публикацию сейчас. |
posts:write |
list_posts |
Список постов, сначала с самым поздним временем публикации, со статусом и ошибкой неудачного. Необязательные status (draft, scheduled, reconnect_wait, published, failed), channel, from, to и limit (от 1 до 50, по умолчанию 20). |
posts:read |
get_post |
Один пост по id: статус, ответ сети и журнал попыток (attempt_log). |
posts:read |
retry_post |
Ставит неудачный пост (по id) в очередь на повторную публикацию сейчас, со свежим счётчиком попыток. Только пост со статусом failed. |
posts:write |
get_account |
Показывает, от чьего имени действует ключ: команда, тариф и срок его действия, лимиты (использовано и осталось постов, число каналов, запросов в минуту) и сам ключ с областями. Без аргументов. | не нужна |
get_channel |
Один канал по id: сеть, статус и всё, что он публикует, включая пределы для изображений и видео. |
channels:read |
get_next_slot |
Ближайший свободный слот публикации одного канала (id): момент его расписания, на который ещё нет запланированного поста, в ISO 8601; подставьте его в publish_at. У канала без слотов или без свободного слота в ближайшие 60 дней — null. |
channels:read |
list_sets |
Список наборов каналов: id, название, id каналов набора (передайте их в create_post) и шаблон (текст, ссылка, id файлов, параметры). Необязательный limit (от 1 до 50, по умолчанию 20). |
channels:read |
list_media |
Список файлов медиатеки, сначала новые. Необязательные type (image, video) и limit (от 1 до 50, по умолчанию 20). |
media:read |
get_media |
Один файл медиатеки по id: адрес, тип, размер, разрешение, длина и статус обработки. |
media:read |
delete_media |
Удаляет файл (по id) из медиатеки и хранилища. Необратимо; разрушающее действие. |
media:write |
update_post |
Меняет запланированный пост (по id), который сейчас не публикуется. Передавайте только то, что меняется: publish_at, caption, options. |
posts:write |
delete_post |
Удаляет черновик, запланированный или неудачный пост (по id). Опубликованный пост удалить нельзя. Необратимо; разрушающее действие. |
posts:write |
link_post |
Для неудачного поста с неопределённой ошибкой: принимает id и url поста, который всё-таки вышел, и помечает пост опубликованным. |
posts:write |
list_notifications |
Список уведомлений автора ключа, сначала новые. Необязательные unread (true) и limit (от 1 до 50, по умолчанию 20). |
notifications:read |
mark_notifications_read |
Отмечает прочитанным одно уведомление (id) или все, если id не указан. |
notifications:write |
list_analytics |
Список каналов команды, у которых есть отчёт аналитики, по сетям. | analytics:read |
get_channel_analytics |
Отчёт аналитики одного канала (id) за период: необязательные from и to в ISO 8601 (по умолчанию последние 30 дней, не больше 365). Поля отчёта зависят от сети. |
analytics:read |
Модули тарифа: инструментам для каналов (list_channels, get_channel, get_next_slot) нужен модуль каналов, list_sets — модуль наборов,
для медиа (upload_media_from_url, list_media, get_media, delete_media) — модуль файлов,
для постов (create_post, list_posts, get_post, retry_post, update_post, delete_post,
link_post) — модуль публикаций, для аналитики (list_analytics, get_channel_analytics) —
модуль аналитики. get_account, list_notifications и mark_notifications_read модуля не
требуют, а get_account не требует и области — как их маршруты REST.
Инструменты, которые удаляют (delete_media, delete_post), помечены как разрушающие, а те, что
меняют данные, — как не только для чтения, поэтому ассистент спрашивает человека, прежде чем их
вызвать.
Каждый результат содержит короткий текст для ассистента и данные в structuredContent; поля
поста те же, что в REST API (id, channel, type, caption, link, media, status,
publish_at, retry_at, attempts, result, url).
Время в результатах и аргументах инструментов — ISO 8601. В результатах используется часовой пояс
автора ключа (например, 2026-11-05T10:30:00+03:00). publish_at без смещения тоже читается в
этом поясе и должен быть в будущем.
Отказ приходит как обычный результат инструмента с isError: true, а не как ошибка JSON-RPC.
Текст начинается с кода ошибки API, затем сообщение, а для ошибок валидации — по строке на поле:
{
"content": [
{
"type": "text",
"text": "insufficient_scope: The key does not carry the scope of the operation."
}
],
"isError": true
}
Коды те же, что у REST API (insufficient_scope, module_not_in_plan, validation_failed,
not_found, not_retryable, post_quota, rate_limited и другие). Неизвестное имя
инструмента — ошибка JSON-RPC.
Создайте ключ на /app/api-keys с областями из раздела выше. Те же подсказки, с кнопкой
копирования адреса, есть на этой странице. Замените <key> своим ключом.
claude mcp add --transport http followers https://followers.vc/api/mcp --header "Authorization: Bearer <key>"
В claude_desktop_config.json, через мост mcp-remote (нужен Node.js):
{
"mcpServers": {
"followers": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://followers.vc/api/mcp",
"--header",
"Authorization:${FOLLOWERS_AUTH}"
],
"env": {
"FOLLOWERS_AUTH": "Bearer <key>"
}
}
}
}
В .cursor/mcp.json или в глобальном ~/.cursor/mcp.json:
{
"mcpServers": {
"followers": {
"url": "https://followers.vc/api/mcp",
"headers": {
"Authorization": "Bearer <key>"
}
}
}
}
curl -X POST https://followers.vc/api/mcp \
-H "Authorization: Bearer fvc_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'
followers.vc может уведомлять ваш сервер о том, что происходит с вашими постами и каналами. Создайте
вебхук на странице Webhooks вашего рабочего пространства (/app/webhooks): укажите адрес https,
выберите события и, при необходимости, каналы. Вебхуки входят не во все тарифы; тариф также задаёт,
сколько активных вебхуков может быть в рабочем пространстве.
| Событие | Когда |
|---|---|
post.scheduled |
Пост запланирован на определённое время (одно событие на канал и время). |
post.published |
Пост опубликован в соцсети. |
post.failed |
Публикация поста окончательно не удалась: автоматических попыток больше не будет. |
channel.disconnected |
Канал потерял доступ и требует переподключения; его посты ждут. |
approval.approved |
Проверяющий одобрил пост на странице согласования. |
approval.changes_requested |
Проверяющий запросил правки поста на странице согласования. |
ping |
Отправляется только кнопкой Send test. |
Вебхуки, ограниченные несколькими каналами, получают события постов и каналов только этих каналов.
Каждое событие — это POST с телом в JSON:
{
"id": "5b0c2a4e-1f0e-4c55-9a43-2d8c7e1b6f10",
"event": "post.published",
"created_at": "2026-10-16T09:30:00Z",
"data": {
"post": {
"id": "kQ7x9a1",
"status": "published",
"type": "media",
"time_post": "2026-10-16T09:30:00Z",
"url": "https://t.me/example/123"
},
"channel": { "id": "a8d3f0c2", "network": "telegram", "name": "Example" }
}
}
id идентифицирует событие. Он остаётся прежним при повторе события и при ручной повторной
отправке, поэтому по нему можно пропускать дубликаты.data.post.status принимает значения scheduled, published или failed; url заполнен у
опубликованного поста, если соцсеть возвращает ссылку.post.failed добавляет "error": {"class": "permanent", "message": "…"}. Класс — одно из
permanent, transient, uncertain (пост мог быть опубликован), auth.channel.disconnected тело "data": {"channel": {…}, "reason": "…"}.approval.approved и approval.changes_requested тело "data": {"approval": {"id": "…", "status": "approved", "decided_by": "…", "decided_at": "…", "comment": "…"}, "posts": [{"post": {…}, "channel": {…}}, …]} — по одному элементу на каждый канал, который охватывает согласование;
post.status может быть draft. Вебхук, ограниченный несколькими каналами, получает событие,
если согласование охватывает любой из них.ping тело "data": {"webhook": {"id": "…", "events": […]}, "message": "…"}.Токены, пароли и другие секреты никогда не передаются.
Заголовки:
| Заголовок | Значение |
|---|---|
Content-Type |
application/json |
User-Agent |
followers.vc-Webhooks/1.0 |
X-Followers-Event |
событие, например post.published |
X-Followers-Delivery |
идентификатор этой доставки (при ручной повторной отправке новый) |
X-Followers-Timestamp |
Unix-время подписи запроса |
X-Followers-Signature |
t=<timestamp>,v1=<signature> |
У каждого вебхука есть секрет (whsec_…). Он показывается один раз — при создании вебхука или при
замене секрета. Подпись — это HMAC-SHA256 от <timestamp>.<raw body> с этим секретом в
шестнадцатеричном виде.
t и v1 из X-Followers-Signature.hex(hmac_sha256(secret, t + "." + body)) и сравните с v1 за постоянное время.t отличается от ваших часов более чем на 5 минут.PHP:
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_FOLLOWERS_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', ($sig['t'] ?? '').'.'.$body, getenv('FOLLOWERS_WEBHOOK_SECRET'));
if (! isset($sig['t'], $sig['v1']) || ! hash_equals($expected, $sig['v1']) || abs(time() - (int) $sig['t']) > 300) {
http_response_code(400);
exit;
}
Node.js:
const crypto = require('crypto');
function verify(secret, header, rawBody) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
return fresh && parts.v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
Отвечайте любым кодом 2xx в течение 10 секунд, а работу выполняйте уже после ответа. Тело вашего
ответа не используется (первый килобайт сохраняется в журнале доставок). Редиректы не выполняются.
429 или 5xx: запрос повторяется через 1 минуту, 5 минут, 30 минут,
2 часа и 12 часов, после чего доставка помечается как неудачная.4xx, 3xx): доставка сразу считается неудачной, без повторов.Журнал доставок каждого вебхука хранит все доставки за 30 дней, с кодом ответа, временем и ошибкой, и позволяет отправить доставку повторно. Замена секрета вступает в силу сразу.
Адрес должен быть публичным: адреса в частных сетях, localhost и обычный http отклоняются —
и при сохранении вебхука, и перед каждым запросом.
Ключ рабочего пространства fvc_ + 40 символов в заголовке Authorization: Bearer <key> (никогда не в строке запроса). Области доступа: channels:read, media:read, media:write, posts:read, posts:write, notifications:read, notifications:write, analytics:read.
curl https://followers.vc/api/v1/me \ -H "Authorization: Bearer $FOLLOWERS_API_KEY"
У всех ошибок одинаковое тело: код постоянен, сообщение — для людей.
{"error": {"code": "validation_failed", "message": "The given data was invalid.", "fields": {"caption": ["The caption field must be a string."]}}}
| Статус | Код | Значение |
|---|---|---|
| 401 | unauthenticated | Ключ не передан, неизвестен или отозван, либо его автор больше не активен в команде. |
| 403 | api_not_in_plan | Тариф команды не включает доступ к API. |
| 403 | plan_expired | Тариф владельца команды истёк. |
| 403 | module_not_in_plan | Тариф команды или роль автора ключа в ней не включает этот ресурс. |
| 403 | insufficient_scope | У ключа нет области доступа, нужной для операции. |
| 403 | storage_limit | Хранилище медиафайлов по тарифу заполнено. |
| 403 | file_too_large | Файл больше, чем допускает тариф. |
| 403 | post_quota | Квота постов по тарифу исчерпана. |
| 403 | analytics_not_in_plan | Тариф не включает аналитику для соцсети этого канала. |
| 404 | not_found | Такой записи нет в команде ключа (или среди уведомлений его автора). |
| 404 | analytics_unavailable | У соцсети этого канала нет аналитики. |
| 405 | method_not_allowed | Путь существует, а метод — нет. |
| 409 | not_editable | Изменить можно только запланированный пост, который сейчас не публикуется. |
| 409 | not_deletable | Удалить можно только черновик, запланированный пост или пост с ошибкой, который сейчас не публикуется. |
| 409 | not_retryable | Повторно опубликовать можно только пост с ошибкой. |
| 409 | not_linkable | Привязать к записи в сети можно только неудачный пост с неясной ошибкой. |
| 409 | idempotency_key_in_use | Запрос с этим Idempotency-Key ещё обрабатывается. |
| 413 | payload_too_large | Тело запроса больше, чем принимает сервер. |
| 422 | validation_failed | Запрос некорректен; в fields перечислены поля с их сообщениями.
|
| 422 | idempotency_key_reused | Этот Idempotency-Key уже использовался с другим запросом. |
| 422 | not_saved | Пост не сохранён по причине, не связанной с полем. |
| 429 | rate_limited | Слишком много запросов для этого ключа; подождите Retry-After секунд.
|
| 500 | server_error | Что-то пошло не так на нашей стороне. Подробности не передаются. |
| 502 | provider_error | Не удалось запросить отчёт у соцсети. Повторите позже. |
| Заголовок | Значение |
|---|---|
X-RateLimit-Limit | Запросов в минуту для этого ключа (определяется тарифом, по умолчанию 60). Отсутствует, если тариф не задаёт лимит. |
X-RateLimit-Remaining | Запросов осталось в текущей минуте. |
Retry-After | Сколько секунд ждать до следующего запроса. |
WWW-Authenticate | Bearer.
|
Idempotent-Replayed | true, если ответ — сохранённый первый ответ для этого Idempotency-Key.
|
Команда, тариф и ключ, от имени которых выполняется запрос.
Команда ключа, её тариф, лимиты и сам ключ. Область доступа не требуется.
curl -X GET https://followers.vc/api/v1/me \ -H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": {
"team": {
"id": "t-91ac0f",
"name": "Acme"
},
"plan": {
"name": "Pro",
"expires_at": 1793000000
},
"limits": {
"rate_limit": 60,
"posts": {
"limit": 300,
"used": 12,
"left": 288
},
"max_channels": 10
},
"key": {
"name": "n8n",
"prefix": "fvc_a1B2",
"scopes": null
}
}
}
| Поле | Тип | Описание |
|---|---|---|
data | Me | |
data.team | object | |
data.team.id | string | Публичный идентификатор команды. |
data.team.name | string | |
data.plan | object | |
data.plan.name | string | null | |
data.plan.expires_at | integer | null | Когда истекает тариф; null — не истекает. Unix-секунды. |
data.limits | object | |
data.limits.rate_limit | integer | Запросов в минуту для этого ключа; -1 — без ограничения. |
data.limits.posts | object | |
data.limits.posts.limit | integer | -1 — без ограничения. |
data.limits.posts.used | integer | |
data.limits.posts.left | integer | |
data.limits.max_channels | integer | -1 — без ограничения. |
data.key | object | |
data.key.name | string | |
data.key.prefix | string | Первые 8 символов ключа. |
data.key.scopes | array of string | null | null — все области доступа. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired |
| 429 | rate_limited |
| 500 | server_error |
Аккаунты соцсетей, подключённые к рабочему пространству.
Каналы команды, сначала старые, с их состоянием и тем, что публикует каждый из них.
| Имя | Где | Тип | Описание |
|---|---|---|---|
network |
query | string | Только каналы этой соцсети, например telegram. At most 50 characters.
|
page |
query | integer | Страница, начиная с 1. Minimum 1. Default 1.
|
per_page |
query | integer | Число элементов на странице; значение больше 100 урезается до 100. Minimum 1. Default 25.
|
curl -X GET https://followers.vc/api/v1/channels \ -H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": [
{
"id": "a8d3f0c2e1b94f7d",
"network": "telegram",
"category": "channel",
"name": "Acme news",
"username": "acmenews",
"avatar": "https://followers.vc/storage/avatar.png",
"status": "active",
"reason": null,
"capabilities": {
"types": [
"text",
"link",
"media"
],
"max_length": {
"text": 4096,
"link": 4096,
"media": 1024
},
"max_media": 10,
"images": true,
"videos": true,
"files": false,
"mixed": false,
"limits": {
"image": null,
"video": null
}
}
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 40
},
"links": {
"next": "https://followers.vc/api/v1/posts?per_page=25&page=2",
"prev": null
}
}
| Поле | Тип | Описание |
|---|---|---|
data | array of Channel | |
data[].id | string | Публичный идентификатор канала. |
data[].network | string | Соцсеть. |
data[].category | string | null | profile, page, channel, group… |
data[].name | string | null | |
data[].username | string | null | |
data[].avatar | string | null | Адрес аватара. |
data[].status | string | needs_reconnect — канал потерял доступ; его посты ждут. One of active, paused, needs_reconnect.
|
data[].reason | string | null | Почему канал нужно переподключить; для любого другого статуса — null. |
data[].capabilities | ChannelCapabilities | null | Что публикует канал. |
data[].capabilities.types | array of string | Типы постов, которые публикует канал. |
data[].capabilities.max_length | object | Максимальная длина текста по типам постов, в символах; null — без ограничения. |
data[].capabilities.max_media | integer | Число файлов в одном посте. |
data[].capabilities.images | boolean | Принимает изображения. |
data[].capabilities.videos | boolean | Принимает видео. |
data[].capabilities.files | boolean | Принимает другие файлы (аудио, документы). |
data[].capabilities.mixed | boolean | Изображения и видео в одном посте. |
data[].capabilities.limits | object | |
data[].capabilities.limits.image | object | |
data[].capabilities.limits.image.max_bytes | integer | null | Максимальный размер изображения; null — неизвестен. |
data[].capabilities.limits.image.aspect | AspectRange | null | Самое узкое и самое широкое соотношение, ширина / высота. |
data[].capabilities.limits.image.aspect.min | number | |
data[].capabilities.limits.image.aspect.max | number | |
data[].capabilities.limits.video | object | |
data[].capabilities.limits.video.max_bytes | integer | null | Максимальный размер видео. |
data[].capabilities.limits.video.min_seconds | number | null | Минимальная длительность видео. |
data[].capabilities.limits.video.max_seconds | number | null | Максимальная длительность видео. |
data[].capabilities.limits.video.aspect | AspectRange | null | Самое узкое и самое широкое соотношение, ширина / высота. |
data[].capabilities.limits.video.aspect.min | number | |
data[].capabilities.limits.video.aspect.max | number | |
data[].capabilities.limits.video.min_width | integer | null | |
data[].capabilities.limits.video.min_height | integer | null | |
data[].capabilities.limits.video.max_width | integer | null | |
data[].capabilities.limits.video.max_height | integer | null | |
data[].capabilities.limits.video.codecs | array of string | null | Видеокодеки в том виде, как их называет ffprobe. |
meta | object | |
meta.page | integer | Текущая страница, начиная с 1. |
meta.per_page | integer | Элементов на странице (по умолчанию 25, не более 100). |
meta.total | integer | Элементов на всех страницах. |
links | object | |
links.next | string | null | Следующая страница; на последней — null. |
links.prev | string | null | Предыдущая страница; на первой — null. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 422 | validation_failed |
| 429 | rate_limited |
| 500 | server_error |
Один канал команды.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор канала. |
curl -X GET https://followers.vc/api/v1/channels/{id} \
-H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": {
"id": "a8d3f0c2e1b94f7d",
"network": "telegram",
"category": "channel",
"name": "Acme news",
"username": "acmenews",
"avatar": "https://followers.vc/storage/avatar.png",
"status": "active",
"reason": null,
"capabilities": {
"types": [
"text",
"link",
"media"
],
"max_length": {
"text": 4096,
"link": 4096,
"media": 1024
},
"max_media": 10,
"images": true,
"videos": true,
"files": false,
"mixed": false,
"limits": {
"image": {
"max_bytes": 10485760,
"aspect": null
},
"video": {
"max_bytes": 52428800,
"min_seconds": 3,
"max_seconds": 90,
"aspect": null,
"min_width": null,
"min_height": null,
"max_width": 1920,
"max_height": 1920,
"codecs": [
"h264",
"hevc"
]
}
}
}
}
}
| Поле | Тип | Описание |
|---|---|---|
data | Channel | |
data.id | string | Публичный идентификатор канала. |
data.network | string | Соцсеть. |
data.category | string | null | profile, page, channel, group… |
data.name | string | null | |
data.username | string | null | |
data.avatar | string | null | Адрес аватара. |
data.status | string | needs_reconnect — канал потерял доступ; его посты ждут. One of active, paused, needs_reconnect.
|
data.reason | string | null | Почему канал нужно переподключить; для любого другого статуса — null. |
data.capabilities | ChannelCapabilities | null | Что публикует канал. |
data.capabilities.types | array of string | Типы постов, которые публикует канал. |
data.capabilities.max_length | object | Максимальная длина текста по типам постов, в символах; null — без ограничения. |
data.capabilities.max_media | integer | Число файлов в одном посте. |
data.capabilities.images | boolean | Принимает изображения. |
data.capabilities.videos | boolean | Принимает видео. |
data.capabilities.files | boolean | Принимает другие файлы (аудио, документы). |
data.capabilities.mixed | boolean | Изображения и видео в одном посте. |
data.capabilities.limits | object | |
data.capabilities.limits.image | object | |
data.capabilities.limits.image.max_bytes | integer | null | Максимальный размер изображения; null — неизвестен. |
data.capabilities.limits.image.aspect | AspectRange | null | Самое узкое и самое широкое соотношение, ширина / высота. |
data.capabilities.limits.image.aspect.min | number | |
data.capabilities.limits.image.aspect.max | number | |
data.capabilities.limits.video | object | |
data.capabilities.limits.video.max_bytes | integer | null | Максимальный размер видео. |
data.capabilities.limits.video.min_seconds | number | null | Минимальная длительность видео. |
data.capabilities.limits.video.max_seconds | number | null | Максимальная длительность видео. |
data.capabilities.limits.video.aspect | AspectRange | null | Самое узкое и самое широкое соотношение, ширина / высота. |
data.capabilities.limits.video.aspect.min | number | |
data.capabilities.limits.video.aspect.max | number | |
data.capabilities.limits.video.min_width | integer | null | |
data.capabilities.limits.video.min_height | integer | null | |
data.capabilities.limits.video.max_width | integer | null | |
data.capabilities.limits.video.max_height | integer | null | |
data.capabilities.limits.video.codecs | array of string | null | Видеокодеки в том виде, как их называет ffprobe. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 404 | not_found |
| 429 | rate_limited |
| 500 | server_error |
Ближайший свободный слот публикации одного канала: время, которое редактор подставляет в поле времени для «ближайшего свободного слота». Слот занят, если у канала уже есть запланированный пост на эту минуту. У канала без слотов или без свободного слота в ближайшие 60 дней next_slot: null.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор канала. |
curl -X GET https://followers.vc/api/v1/channels/{id}/next-slot \
-H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": {
"channel": "a8d3f0c2e1b94f7d",
"next_slot": 1792569600,
"slots": {
"times": [
"09:00"
],
"days": [
1
],
"timezone": "Europe/Moscow"
}
}
}
| Поле | Тип | Описание |
|---|---|---|
data | NextSlot | |
data.channel | string | Публичный идентификатор канала. |
data.next_slot | integer | null | Unix-время ближайшего свободного слота; null, если у канала нет слотов или свободного нет в ближайшие 60 дней. |
data.slots | PostingSlots | null | Слоты публикации канала: время суток в выбранные дни недели в одном часовом поясе. |
data.slots.times | array of string | Время суток, HH:MM.
|
data.slots.days | array of integer | Дни недели по ISO, понедельник — 1. |
data.slots.timezone | string | Часовой пояс времени. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 404 | not_found |
| 429 | rate_limited |
| 500 | server_error |
Наборы каналов команды, сначала недавно изменённые: название, каналы набора и шаблон (текст, ссылка, файлы, параметры), который редактор подставляет в пустые поля.
curl -X GET https://followers.vc/api/v1/sets \ -H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": [
{
"id": "b7e4c1a9d2f0",
"name": "Shops",
"color": "primary",
"channels": [
"string"
],
"template": {
"caption": "string",
"link": "string",
"media": [
null
],
"options": {}
}
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 40
},
"links": {
"next": "https://followers.vc/api/v1/posts?per_page=25&page=2",
"prev": null
}
}
| Поле | Тип | Описание |
|---|---|---|
data | array of ChannelSet | |
data[].id | string | Публичный идентификатор набора. |
data[].name | string | null | |
data[].color | string | null | Цвет набора в кабинете. |
data[].channels | array of string | Публичные идентификаторы каналов набора; передайте их в POST /posts.
|
data[].template | SetTemplate | null | Что редактор подставляет в пустые поля при выборе набора; null у набора без шаблона. |
data[].template.caption | string | Текст. |
data[].template.link | string | Ссылка; пусто, если её нет. |
data[].template.media | array of string | Идентификаторы файлов, как в /media.
|
data[].template.options | object | Параметры соцсетей, например fb_type.
|
meta | object | |
meta.page | integer | Текущая страница, начиная с 1. |
meta.per_page | integer | Элементов на странице (по умолчанию 25, не более 100). |
meta.total | integer | Элементов на всех страницах. |
links | object | |
links.next | string | null | Следующая страница; на последней — null. |
links.prev | string | null | Предыдущая страница; на первой — null. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 422 | validation_failed |
| 429 | rate_limited |
| 500 | server_error |
Медиатека: файлы для вложения в посты.
Файлы медиатеки команды, сначала новые. Папки не показываются.
| Имя | Где | Тип | Описание |
|---|---|---|---|
type |
query | string | Только изображения или только видео. One of image, video.
|
page |
query | integer | Страница, начиная с 1. Minimum 1. Default 1.
|
per_page |
query | integer | Число элементов на странице; значение больше 100 урезается до 100. Minimum 1. Default 25.
|
curl -X GET https://followers.vc/api/v1/media \ -H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": [
{
"id": "fil-5f1c0e2a9b",
"name": "photo_x1Y2z3.png",
"url": "https://followers.vc/storage/files/photo.png",
"type": "image",
"mime": "image/png",
"extension": "png",
"size": 48213,
"width": 1080,
"height": 1080,
"duration": null,
"media_status": "ready",
"media_error": null,
"created_at": 1793000000
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 40
},
"links": {
"next": "https://followers.vc/api/v1/posts?per_page=25&page=2",
"prev": null
}
}
| Поле | Тип | Описание |
|---|---|---|
data | array of Media | |
data[].id | string | Публичный идентификатор файла. |
data[].name | string | |
data[].url | string | Публичный адрес файла. |
data[].type | string | null | Вид файла: image, video, pdf, doc, csv… |
data[].mime | string | null | |
data[].extension | string | null | |
data[].size | integer | Байты. |
data[].width | integer | null | Пиксели; null — неизвестно. |
data[].height | integer | null | Пиксели; null — неизвестно. |
data[].duration | number | null | Секунды, для видео. |
data[].media_status | string | null | Фоновая обработка файла: pending, ready или failed; null — файл не обрабатывался (документ или более старый файл). One of pending, ready, failed.
|
data[].media_error | string | null | Почему файл не удалось обработать. |
data[].created_at | integer | null | Время загрузки. Unix-секунды. |
meta | object | |
meta.page | integer | Текущая страница, начиная с 1. |
meta.per_page | integer | Элементов на странице (по умолчанию 25, не более 100). |
meta.total | integer | Элементов на всех страницах. |
links | object | |
links.next | string | null | Следующая страница; на последней — null. |
links.prev | string | null | Предыдущая страница; на первой — null. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 422 | validation_failed |
| 429 | rate_limited |
| 500 | server_error |
Сохраняет загруженный файл в корне медиатеки. Допустимые типы, максимальный размер файла и объём хранилища определяются тарифом, как и в кабинете. Видео обрабатывается в фоне (media_status).
multipart/form-data| Поле | Тип | Описание |
|---|---|---|
file обязательно |
file | Файл. |
curl -X POST https://followers.vc/api/v1/media \ -H "Authorization: Bearer $FOLLOWERS_API_KEY" \ -F "file=@photo.png"
{
"data": {
"id": "fil-5f1c0e2a9b",
"name": "photo_x1Y2z3.png",
"url": "https://followers.vc/storage/files/photo.png",
"type": "image",
"mime": "image/png",
"extension": "png",
"size": 48213,
"width": 1080,
"height": 1080,
"duration": null,
"media_status": "ready",
"media_error": null,
"created_at": 1793000000
}
}
| Поле | Тип | Описание |
|---|---|---|
data | Media | |
data.id | string | Публичный идентификатор файла. |
data.name | string | |
data.url | string | Публичный адрес файла. |
data.type | string | null | Вид файла: image, video, pdf, doc, csv… |
data.mime | string | null | |
data.extension | string | null | |
data.size | integer | Байты. |
data.width | integer | null | Пиксели; null — неизвестно. |
data.height | integer | null | Пиксели; null — неизвестно. |
data.duration | number | null | Секунды, для видео. |
data.media_status | string | null | Фоновая обработка файла: pending, ready или failed; null — файл не обрабатывался (документ или более старый файл). One of pending, ready, failed.
|
data.media_error | string | null | Почему файл не удалось обработать. |
data.created_at | integer | null | Время загрузки. Unix-секунды. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope, storage_limit, file_too_large |
| 413 | payload_too_large |
| 422 | validation_failed |
| 429 | rate_limited |
| 500 | server_error |
Скачивает файл по публичному адресу http/https в медиатеку. Адреса внутри частных сетей отклоняются (422 по полю url).
application/json| Поле | Тип | Описание |
|---|---|---|
url обязательно |
string (uri) | Публичный адрес http или https. At most 2048 characters. |
name |
string | null | Имя, под которым сохранить файл. At most 255 characters. |
curl -X POST https://followers.vc/api/v1/media/from-url \
-H "Authorization: Bearer $FOLLOWERS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/photo.png","name":"launch.png"}'
{
"data": {
"id": "fil-5f1c0e2a9b",
"name": "photo_x1Y2z3.png",
"url": "https://followers.vc/storage/files/photo.png",
"type": "image",
"mime": "image/png",
"extension": "png",
"size": 48213,
"width": 1080,
"height": 1080,
"duration": null,
"media_status": "ready",
"media_error": null,
"created_at": 1793000000
}
}
| Поле | Тип | Описание |
|---|---|---|
data | Media | |
data.id | string | Публичный идентификатор файла. |
data.name | string | |
data.url | string | Публичный адрес файла. |
data.type | string | null | Вид файла: image, video, pdf, doc, csv… |
data.mime | string | null | |
data.extension | string | null | |
data.size | integer | Байты. |
data.width | integer | null | Пиксели; null — неизвестно. |
data.height | integer | null | Пиксели; null — неизвестно. |
data.duration | number | null | Секунды, для видео. |
data.media_status | string | null | Фоновая обработка файла: pending, ready или failed; null — файл не обрабатывался (документ или более старый файл). One of pending, ready, failed.
|
data.media_error | string | null | Почему файл не удалось обработать. |
data.created_at | integer | null | Время загрузки. Unix-секунды. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope, storage_limit, file_too_large |
| 422 | validation_failed |
| 429 | rate_limited |
| 500 | server_error |
Один файл медиатеки.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор файла. |
curl -X GET https://followers.vc/api/v1/media/{id} \
-H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": {
"id": "fil-5f1c0e2a9b",
"name": "photo_x1Y2z3.png",
"url": "https://followers.vc/storage/files/photo.png",
"type": "image",
"mime": "image/png",
"extension": "png",
"size": 48213,
"width": 1080,
"height": 1080,
"duration": null,
"media_status": "ready",
"media_error": null,
"created_at": 1793000000
}
}
| Поле | Тип | Описание |
|---|---|---|
data | Media | |
data.id | string | Публичный идентификатор файла. |
data.name | string | |
data.url | string | Публичный адрес файла. |
data.type | string | null | Вид файла: image, video, pdf, doc, csv… |
data.mime | string | null | |
data.extension | string | null | |
data.size | integer | Байты. |
data.width | integer | null | Пиксели; null — неизвестно. |
data.height | integer | null | Пиксели; null — неизвестно. |
data.duration | number | null | Секунды, для видео. |
data.media_status | string | null | Фоновая обработка файла: pending, ready или failed; null — файл не обрабатывался (документ или более старый файл). One of pending, ready, failed.
|
data.media_error | string | null | Почему файл не удалось обработать. |
data.created_at | integer | null | Время загрузки. Unix-секунды. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 404 | not_found |
| 429 | rate_limited |
| 500 | server_error |
Удаляет файл и его сохранённую копию.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор файла. |
curl -X DELETE https://followers.vc/api/v1/media/{id} \
-H "Authorization: Bearer $FOLLOWERS_API_KEY"
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 404 | not_found |
| 429 | rate_limited |
| 500 | server_error |
Создание, планирование, изменение и повторная отправка постов.
Посты команды, сначала с самым поздним временем публикации.
| Имя | Где | Тип | Описание |
|---|---|---|---|
status |
query | string | Только посты с этим статусом. One of draft, scheduled, reconnect_wait, published, failed.
|
channel |
query | string | Публичный идентификатор канала. At most 64 characters. |
from |
query | string | integer | Время публикации от (включительно). |
to |
query | string | integer | Время публикации до (включительно). |
page |
query | integer | Страница, начиная с 1. Minimum 1. Default 1.
|
per_page |
query | integer | Число элементов на странице; значение больше 100 урезается до 100. Minimum 1. Default 25.
|
curl -X GET https://followers.vc/api/v1/posts \ -H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": [
{
"id": "kQ7x9a1b2c3d",
"channel": {
"id": "a8d3f0c2e1b94f7d",
"network": "telegram",
"name": "Acme news"
},
"type": "text",
"caption": "Hello from the API",
"thread": [
"string"
],
"thread_delay": 0,
"link": null,
"media": [
{
"id": "fil-5f1c0e2a9b",
"url": "https://followers.vc/storage/files/photo.png",
"alt": null
}
],
"options": {},
"status": "scheduled",
"publish_at": 1793000000,
"retry_at": 1793000000,
"attempts": 0,
"result": {
"message": "Bad token",
"error_class": "permanent",
"social_id": null,
"thread": [
null
]
},
"url": null,
"metrics": {
"likes": 42,
"reposts": 7,
"comments": 5,
"quotes": 1,
"views": 1800,
"updated_at": 1793003600
},
"created_at": 1793000000
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 40
},
"links": {
"next": "https://followers.vc/api/v1/posts?per_page=25&page=2",
"prev": null
}
}
| Поле | Тип | Описание |
|---|---|---|
data | array of Post | |
data[].id | string | Публичный идентификатор поста. |
data[].channel | object | |
data[].channel.id | string | null | Публичный идентификатор канала; null, если канал удалён. |
data[].channel.network | string | null | |
data[].channel.name | string | null | |
data[].type | string | text, link или media (у более старых постов могут быть другие типы). |
data[].caption | string | |
data[].thread | array of string | Сообщения, вышедшие после поста: тред из ответов там, где у сети есть треды (X, Bluesky, Mastodon, Threads, Telegram, Discord, Slack), комментарии под постом там, где есть комментарии (Facebook, Instagram, Reddit, ВКонтакте). Пусто, если их нет. |
data[].thread_delay | integer | Минуты между сообщениями треда; 0 — сразу. |
data[].link | string | null | |
data[].media | array of object | Файлы поста по порядку. |
data[].media[].id | string | null | Идентификатор в медиатеке; null, если файла там нет. |
data[].media[].url | string | |
data[].media[].alt | string | null | Описание файла для программ экранного доступа; передаётся в сети, которые его принимают (Bluesky, Mastodon, X, Pinterest). null, если описания нет. |
data[].options | object | Параметры конкретной соцсети в том виде, в каком они были переданы. |
data[].status | string | reconnect_wait — пост запланирован, но канал нужно переподключить. unknown — статус, который API не называет. One of draft, scheduled, reconnect_wait, published, failed, unknown.
|
data[].publish_at | integer | null | Когда пост должен выйти (или должен был выйти); для черновика — null. Unix-секунды. |
data[].retry_at | integer | null | Временная ошибка: очередь повторит попытку в это время. Unix-секунды. |
data[].attempts | integer | Число попыток публикации на данный момент. |
data[].result | PostResult | null | Что вернула последняя попытка публикации. |
data[].result.message | string | null | Сообщение соцсети или followers.vc. |
data[].result.error_class | string | null | transient, uncertain (пост мог быть опубликован), auth или permanent; null — ошибки нет. |
data[].result.social_id | string | integer | null | Идентификатор поста в соцсети. |
data[].result.thread | array of object | Что вышло с каждым сообщением треда, по порядку; публикация останавливается на первом отказе. |
data[].result.thread[].social_id | string | null | Id сообщения в сети. |
data[].result.thread[].url | string | null | Адрес сообщения, если сеть его даёт. |
data[].result.thread[].error | string | null | Почему сеть отклонила сообщение или почему сеть не принимает тред. |
data[].url | string | null | Адрес опубликованного поста, если соцсеть его возвращает. |
data[].metrics | PostMetrics | null | Счётчики опубликованного поста. Счётчик, которого у сети нет, равен null: Bluesky, Mastodon и Reddit не считают просмотры, цитаты есть только у X, Bluesky, Mastodon и Threads. |
data[].metrics.likes | integer | null | Лайки, избранное, реакции. |
data[].metrics.reposts | integer | null | Репосты, ретвиты, бусты. |
data[].metrics.comments | integer | null | Ответы и комментарии. |
data[].metrics.quotes | integer | null | Репосты со своим текстом. |
data[].metrics.views | integer | null | Просмотры или показы. |
data[].metrics.updated_at | integer | Когда счётчики прочитаны. Unix-секунды. |
data[].created_at | integer | null | Время создания. Unix-секунды. |
meta | object | |
meta.page | integer | Текущая страница, начиная с 1. |
meta.per_page | integer | Элементов на странице (по умолчанию 25, не более 100). |
meta.total | integer | Элементов на всех страницах. |
links | object | |
links.next | string | null | Следующая страница; на последней — null. |
links.prev | string | null | Предыдущая страница; на первой — null. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 422 | validation_failed |
| 429 | rate_limited |
| 500 | server_error |
Создаёт по одному посту на каждый канал через тот же планировщик, что и редактор. Без publish_at пост должен выйти сейчас, и его публикует очередь — никогда не внутри запроса. Если какой-либо канал отклоняет пост, ничего не сохраняется: отказ приходит в поле channels.<id>. С заголовком Idempotency-Key тот же ключ и то же тело в течение 24 часов возвращают первый ответ (Idempotent-Replayed: true).
| Имя | Где | Тип | Описание |
|---|---|---|---|
Idempotency-Key |
header | string | Любая уникальная строка длиной до 255 символов. Тот же ключ с тем же телом в течение 24 часов возвращает первый успешный ответ; с другим телом — 422 idempotency_key_reused; пока первый запрос ещё выполняется — 409 idempotency_key_in_use. At most 255 characters.
|
application/json| Поле | Тип | Описание |
|---|---|---|
channels обязательно |
array of string | Публичные идентификаторы каналов команды; на каждый канал создаётся один пост. At least 1 items. At most 50 items. |
type обязательно |
string | One of text, link, media.
|
caption |
string | null | At most 100000 characters. |
thread |
array of string | Сообщения, которые нужно опубликовать после поста: тред или комментарии — как их принимает сеть (см. thread у поста). Сети без того и другого получат только пост. At most 25 items.
|
thread_media |
object | Файлы сообщений треда: номер сообщения в thread (с 0) => до 4 id файлов медиатеки. X, Bluesky и Mastodon публикуют их вместе с сообщением, остальные сети получают только его текст.
|
thread_delay |
integer | Сколько минут ждать между сообщениями треда, от 0 до 60. 0 — каждое сообщение выходит сразу после предыдущего. Minimum 0. Default 0.
|
captions |
object | Свой текст сети по её имени (x, telegram, facebook… — как channel.network у поста): заменяет caption в постах каналов этой сети.
|
signature |
string | Подпись команды, которую нужно поставить под текстом поста и под каждым своим текстом сети: её id или default — подпись по умолчанию. Нужны подписи в тарифе команды. At most 32 characters.
|
channel_captions |
object | Свой текст канала: id канала (одного из channels) => его текст. Заменяет текст поста и свой текст сети (captions) только для этого канала.
|
link |
string | null | Для поста типа link. At most 2048 characters.
|
media |
array of string | null | Идентификаторы из /media, в нужном порядке. At most 50 items.
|
media_alt |
object | Описания файлов для программ экранного доступа: id файла => текст до 1000 знаков. Id, которых нет в media, не учитываются. |
options |
object | null | Параметры конкретной соцсети в том виде, в каком их принимает редактор. |
publish_at |
string | integer | null | ISO 8601 (2026-11-05T10:30:00+03:00, …Z) или unix-секунды. Без смещения — часовой пояс автора ключа. Время должно быть в будущем. Чтобы опубликовать сейчас, не указывайте его: пост попадёт в очередь.
|
channel_publish_at |
object | Своё время канала вместо publish_at: id канала => дата и время ISO 8601 или unix-секунды, в будущем. Нужен publish_at — время остальных каналов. |
curl -X POST https://followers.vc/api/v1/posts \
-H "Authorization: Bearer $FOLLOWERS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"channels":["a8d3f0c2e1b94f7d"],"type":"text","caption":"Hello from the API","thread":["string"],"thread_media":[["fil-5f1c0e2a9b"]],"thread_delay":5,"captions":{"x":"A shorter text for X"},"signature":"default","channel_captions":{"acc-5f1c0e2a9b":"Hello, our page"},"link":null,"media":[],"media_alt":{"fil-5f1c0e2a9b":"A cat asleep on a keyboard"},"options":null,"publish_at":"2026-11-05T10:30:00+03:00","channel_publish_at":{"a8d3f0c2e1b94f7d":"2026-11-05T09:30:00+03:00"}}'
{
"data": [
{
"id": "kQ7x9a1b2c3d",
"channel": {
"id": "a8d3f0c2e1b94f7d",
"network": "telegram",
"name": "Acme news"
},
"type": "text",
"caption": "Hello from the API",
"thread": [
"string"
],
"thread_delay": 0,
"link": null,
"media": [
{
"id": "fil-5f1c0e2a9b",
"url": "https://followers.vc/storage/files/photo.png",
"alt": null
}
],
"options": {},
"status": "scheduled",
"publish_at": 1793000000,
"retry_at": 1793000000,
"attempts": 0,
"result": {
"message": "Bad token",
"error_class": "permanent",
"social_id": null,
"thread": [
{
"social_id": null,
"url": null,
"error": null
}
]
},
"url": null,
"metrics": {
"likes": 42,
"reposts": 7,
"comments": 5,
"quotes": 1,
"views": 1800,
"updated_at": 1793003600
},
"created_at": 1793000000
}
]
}
| Поле | Тип | Описание |
|---|---|---|
data | array of Post | |
data[].id | string | Публичный идентификатор поста. |
data[].channel | object | |
data[].channel.id | string | null | Публичный идентификатор канала; null, если канал удалён. |
data[].channel.network | string | null | |
data[].channel.name | string | null | |
data[].type | string | text, link или media (у более старых постов могут быть другие типы). |
data[].caption | string | |
data[].thread | array of string | Сообщения, вышедшие после поста: тред из ответов там, где у сети есть треды (X, Bluesky, Mastodon, Threads, Telegram, Discord, Slack), комментарии под постом там, где есть комментарии (Facebook, Instagram, Reddit, ВКонтакте). Пусто, если их нет. |
data[].thread_delay | integer | Минуты между сообщениями треда; 0 — сразу. |
data[].link | string | null | |
data[].media | array of object | Файлы поста по порядку. |
data[].media[].id | string | null | Идентификатор в медиатеке; null, если файла там нет. |
data[].media[].url | string | |
data[].media[].alt | string | null | Описание файла для программ экранного доступа; передаётся в сети, которые его принимают (Bluesky, Mastodon, X, Pinterest). null, если описания нет. |
data[].options | object | Параметры конкретной соцсети в том виде, в каком они были переданы. |
data[].status | string | reconnect_wait — пост запланирован, но канал нужно переподключить. unknown — статус, который API не называет. One of draft, scheduled, reconnect_wait, published, failed, unknown.
|
data[].publish_at | integer | null | Когда пост должен выйти (или должен был выйти); для черновика — null. Unix-секунды. |
data[].retry_at | integer | null | Временная ошибка: очередь повторит попытку в это время. Unix-секунды. |
data[].attempts | integer | Число попыток публикации на данный момент. |
data[].result | PostResult | null | Что вернула последняя попытка публикации. |
data[].result.message | string | null | Сообщение соцсети или followers.vc. |
data[].result.error_class | string | null | transient, uncertain (пост мог быть опубликован), auth или permanent; null — ошибки нет. |
data[].result.social_id | string | integer | null | Идентификатор поста в соцсети. |
data[].result.thread | array of object | Что вышло с каждым сообщением треда, по порядку; публикация останавливается на первом отказе. |
data[].result.thread[].social_id | string | null | Id сообщения в сети. |
data[].result.thread[].url | string | null | Адрес сообщения, если сеть его даёт. |
data[].result.thread[].error | string | null | Почему сеть отклонила сообщение или почему сеть не принимает тред. |
data[].url | string | null | Адрес опубликованного поста, если соцсеть его возвращает. |
data[].metrics | PostMetrics | null | Счётчики опубликованного поста. Счётчик, которого у сети нет, равен null: Bluesky, Mastodon и Reddit не считают просмотры, цитаты есть только у X, Bluesky, Mastodon и Threads. |
data[].metrics.likes | integer | null | Лайки, избранное, реакции. |
data[].metrics.reposts | integer | null | Репосты, ретвиты, бусты. |
data[].metrics.comments | integer | null | Ответы и комментарии. |
data[].metrics.quotes | integer | null | Репосты со своим текстом. |
data[].metrics.views | integer | null | Просмотры или показы. |
data[].metrics.updated_at | integer | Когда счётчики прочитаны. Unix-секунды. |
data[].created_at | integer | null | Время создания. Unix-секунды. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope, post_quota |
| 409 | idempotency_key_in_use |
| 422 | validation_failed, idempotency_key_reused, not_saved |
| 429 | rate_limited |
| 500 | server_error |
Один пост с попытками публикации.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор поста. |
curl -X GET https://followers.vc/api/v1/posts/{id} \
-H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": {
"id": "kQ7x9a1b2c3d",
"channel": {
"id": "a8d3f0c2e1b94f7d",
"network": "telegram",
"name": "Acme news"
},
"type": "text",
"caption": "Hello from the API",
"thread": [
"string"
],
"thread_delay": 0,
"link": null,
"media": [
{
"id": "fil-5f1c0e2a9b",
"url": "https://followers.vc/storage/files/photo.png",
"alt": null
}
],
"options": {},
"status": "scheduled",
"publish_at": 1793000000,
"retry_at": 1793000000,
"attempts": 0,
"result": {
"message": "Bad token",
"error_class": "permanent",
"social_id": null,
"thread": [
{
"social_id": null,
"url": null,
"error": null
}
]
},
"url": null,
"metrics": {
"likes": 42,
"reposts": 7,
"comments": 5,
"quotes": 1,
"views": 1800,
"updated_at": 1793003600
},
"created_at": 1793000000,
"attempt_log": [
{
"attempt": 1,
"outcome": "retry",
"error_class": "transient",
"message": "Rate limit",
"at": 1793000000
}
]
}
}
| Поле | Тип | Описание |
|---|---|---|
data | PostDetail | |
data.id | string | Публичный идентификатор поста. |
data.channel | object | |
data.channel.id | string | null | Публичный идентификатор канала; null, если канал удалён. |
data.channel.network | string | null | |
data.channel.name | string | null | |
data.type | string | text, link или media (у более старых постов могут быть другие типы). |
data.caption | string | |
data.thread | array of string | Сообщения, вышедшие после поста: тред из ответов там, где у сети есть треды (X, Bluesky, Mastodon, Threads, Telegram, Discord, Slack), комментарии под постом там, где есть комментарии (Facebook, Instagram, Reddit, ВКонтакте). Пусто, если их нет. |
data.thread_delay | integer | Минуты между сообщениями треда; 0 — сразу. |
data.link | string | null | |
data.media | array of object | Файлы поста по порядку. |
data.media[].id | string | null | Идентификатор в медиатеке; null, если файла там нет. |
data.media[].url | string | |
data.media[].alt | string | null | Описание файла для программ экранного доступа; передаётся в сети, которые его принимают (Bluesky, Mastodon, X, Pinterest). null, если описания нет. |
data.options | object | Параметры конкретной соцсети в том виде, в каком они были переданы. |
data.status | string | reconnect_wait — пост запланирован, но канал нужно переподключить. unknown — статус, который API не называет. One of draft, scheduled, reconnect_wait, published, failed, unknown.
|
data.publish_at | integer | null | Когда пост должен выйти (или должен был выйти); для черновика — null. Unix-секунды. |
data.retry_at | integer | null | Временная ошибка: очередь повторит попытку в это время. Unix-секунды. |
data.attempts | integer | Число попыток публикации на данный момент. |
data.result | PostResult | null | Что вернула последняя попытка публикации. |
data.result.message | string | null | Сообщение соцсети или followers.vc. |
data.result.error_class | string | null | transient, uncertain (пост мог быть опубликован), auth или permanent; null — ошибки нет. |
data.result.social_id | string | integer | null | Идентификатор поста в соцсети. |
data.result.thread | array of object | Что вышло с каждым сообщением треда, по порядку; публикация останавливается на первом отказе. |
data.result.thread[].social_id | string | null | Id сообщения в сети. |
data.result.thread[].url | string | null | Адрес сообщения, если сеть его даёт. |
data.result.thread[].error | string | null | Почему сеть отклонила сообщение или почему сеть не принимает тред. |
data.url | string | null | Адрес опубликованного поста, если соцсеть его возвращает. |
data.metrics | PostMetrics | null | Счётчики опубликованного поста. Счётчик, которого у сети нет, равен null: Bluesky, Mastodon и Reddit не считают просмотры, цитаты есть только у X, Bluesky, Mastodon и Threads. |
data.metrics.likes | integer | null | Лайки, избранное, реакции. |
data.metrics.reposts | integer | null | Репосты, ретвиты, бусты. |
data.metrics.comments | integer | null | Ответы и комментарии. |
data.metrics.quotes | integer | null | Репосты со своим текстом. |
data.metrics.views | integer | null | Просмотры или показы. |
data.metrics.updated_at | integer | Когда счётчики прочитаны. Unix-секунды. |
data.created_at | integer | null | Время создания. Unix-секунды. |
data.attempt_log | array of object | Все попытки публикации, сначала старые. |
data.attempt_log[].attempt | integer | |
data.attempt_log[].outcome | string | published, retry, failed или reconnect. |
data.attempt_log[].error_class | string | null | transient, uncertain, auth или permanent. |
data.attempt_log[].message | string | null | |
data.attempt_log[].at | integer | null | Время попытки. Unix-секунды. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 404 | not_found |
| 429 | rate_limited |
| 500 | server_error |
Переносит или редактирует запланированный пост, который сейчас не публикуется.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор поста. |
application/json| Поле | Тип | Описание |
|---|---|---|
caption |
string | null | At most 100000 characters. |
options |
object | null | |
publish_at |
string | integer | null | ISO 8601 (2026-11-05T10:30:00+03:00, …Z) или unix-секунды. Время должно быть в будущем.
|
curl -X PATCH https://followers.vc/api/v1/posts/{id} \
-H "Authorization: Bearer $FOLLOWERS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"caption":"New text","options":null,"publish_at":1793000000}'
{
"data": {
"id": "kQ7x9a1b2c3d",
"channel": {
"id": "a8d3f0c2e1b94f7d",
"network": "telegram",
"name": "Acme news"
},
"type": "text",
"caption": "Hello from the API",
"thread": [
"string"
],
"thread_delay": 0,
"link": null,
"media": [
{
"id": "fil-5f1c0e2a9b",
"url": "https://followers.vc/storage/files/photo.png",
"alt": null
}
],
"options": {},
"status": "scheduled",
"publish_at": 1793000000,
"retry_at": 1793000000,
"attempts": 0,
"result": {
"message": "Bad token",
"error_class": "permanent",
"social_id": null,
"thread": [
{
"social_id": null,
"url": null,
"error": null
}
]
},
"url": null,
"metrics": {
"likes": 42,
"reposts": 7,
"comments": 5,
"quotes": 1,
"views": 1800,
"updated_at": 1793003600
},
"created_at": 1793000000,
"attempt_log": [
{
"attempt": 1,
"outcome": "retry",
"error_class": "transient",
"message": "Rate limit",
"at": 1793000000
}
]
}
}
| Поле | Тип | Описание |
|---|---|---|
data | PostDetail | |
data.id | string | Публичный идентификатор поста. |
data.channel | object | |
data.channel.id | string | null | Публичный идентификатор канала; null, если канал удалён. |
data.channel.network | string | null | |
data.channel.name | string | null | |
data.type | string | text, link или media (у более старых постов могут быть другие типы). |
data.caption | string | |
data.thread | array of string | Сообщения, вышедшие после поста: тред из ответов там, где у сети есть треды (X, Bluesky, Mastodon, Threads, Telegram, Discord, Slack), комментарии под постом там, где есть комментарии (Facebook, Instagram, Reddit, ВКонтакте). Пусто, если их нет. |
data.thread_delay | integer | Минуты между сообщениями треда; 0 — сразу. |
data.link | string | null | |
data.media | array of object | Файлы поста по порядку. |
data.media[].id | string | null | Идентификатор в медиатеке; null, если файла там нет. |
data.media[].url | string | |
data.media[].alt | string | null | Описание файла для программ экранного доступа; передаётся в сети, которые его принимают (Bluesky, Mastodon, X, Pinterest). null, если описания нет. |
data.options | object | Параметры конкретной соцсети в том виде, в каком они были переданы. |
data.status | string | reconnect_wait — пост запланирован, но канал нужно переподключить. unknown — статус, который API не называет. One of draft, scheduled, reconnect_wait, published, failed, unknown.
|
data.publish_at | integer | null | Когда пост должен выйти (или должен был выйти); для черновика — null. Unix-секунды. |
data.retry_at | integer | null | Временная ошибка: очередь повторит попытку в это время. Unix-секунды. |
data.attempts | integer | Число попыток публикации на данный момент. |
data.result | PostResult | null | Что вернула последняя попытка публикации. |
data.result.message | string | null | Сообщение соцсети или followers.vc. |
data.result.error_class | string | null | transient, uncertain (пост мог быть опубликован), auth или permanent; null — ошибки нет. |
data.result.social_id | string | integer | null | Идентификатор поста в соцсети. |
data.result.thread | array of object | Что вышло с каждым сообщением треда, по порядку; публикация останавливается на первом отказе. |
data.result.thread[].social_id | string | null | Id сообщения в сети. |
data.result.thread[].url | string | null | Адрес сообщения, если сеть его даёт. |
data.result.thread[].error | string | null | Почему сеть отклонила сообщение или почему сеть не принимает тред. |
data.url | string | null | Адрес опубликованного поста, если соцсеть его возвращает. |
data.metrics | PostMetrics | null | Счётчики опубликованного поста. Счётчик, которого у сети нет, равен null: Bluesky, Mastodon и Reddit не считают просмотры, цитаты есть только у X, Bluesky, Mastodon и Threads. |
data.metrics.likes | integer | null | Лайки, избранное, реакции. |
data.metrics.reposts | integer | null | Репосты, ретвиты, бусты. |
data.metrics.comments | integer | null | Ответы и комментарии. |
data.metrics.quotes | integer | null | Репосты со своим текстом. |
data.metrics.views | integer | null | Просмотры или показы. |
data.metrics.updated_at | integer | Когда счётчики прочитаны. Unix-секунды. |
data.created_at | integer | null | Время создания. Unix-секунды. |
data.attempt_log | array of object | Все попытки публикации, сначала старые. |
data.attempt_log[].attempt | integer | |
data.attempt_log[].outcome | string | published, retry, failed или reconnect. |
data.attempt_log[].error_class | string | null | transient, uncertain, auth или permanent. |
data.attempt_log[].message | string | null | |
data.attempt_log[].at | integer | null | Время попытки. Unix-секунды. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 404 | not_found |
| 409 | not_editable |
| 422 | validation_failed |
| 429 | rate_limited |
| 500 | server_error |
Удаляет черновик, запланированный пост или пост с ошибкой, который сейчас не публикуется.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор поста. |
curl -X DELETE https://followers.vc/api/v1/posts/{id} \
-H "Authorization: Bearer $FOLLOWERS_API_KEY"
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 404 | not_found |
| 409 | not_deletable |
| 429 | rate_limited |
| 500 | server_error |
Возвращает пост с ошибкой в очередь.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор поста. |
curl -X POST https://followers.vc/api/v1/posts/{id}/retry \
-H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": {
"id": "kQ7x9a1b2c3d",
"channel": {
"id": "a8d3f0c2e1b94f7d",
"network": "telegram",
"name": "Acme news"
},
"type": "text",
"caption": "Hello from the API",
"thread": [
"string"
],
"thread_delay": 0,
"link": null,
"media": [
{
"id": "fil-5f1c0e2a9b",
"url": "https://followers.vc/storage/files/photo.png",
"alt": null
}
],
"options": {},
"status": "scheduled",
"publish_at": 1793000000,
"retry_at": 1793000000,
"attempts": 0,
"result": {
"message": "Bad token",
"error_class": "permanent",
"social_id": null,
"thread": [
{
"social_id": null,
"url": null,
"error": null
}
]
},
"url": null,
"metrics": {
"likes": 42,
"reposts": 7,
"comments": 5,
"quotes": 1,
"views": 1800,
"updated_at": 1793003600
},
"created_at": 1793000000,
"attempt_log": [
{
"attempt": 1,
"outcome": "retry",
"error_class": "transient",
"message": "Rate limit",
"at": 1793000000
}
]
}
}
| Поле | Тип | Описание |
|---|---|---|
data | PostDetail | |
data.id | string | Публичный идентификатор поста. |
data.channel | object | |
data.channel.id | string | null | Публичный идентификатор канала; null, если канал удалён. |
data.channel.network | string | null | |
data.channel.name | string | null | |
data.type | string | text, link или media (у более старых постов могут быть другие типы). |
data.caption | string | |
data.thread | array of string | Сообщения, вышедшие после поста: тред из ответов там, где у сети есть треды (X, Bluesky, Mastodon, Threads, Telegram, Discord, Slack), комментарии под постом там, где есть комментарии (Facebook, Instagram, Reddit, ВКонтакте). Пусто, если их нет. |
data.thread_delay | integer | Минуты между сообщениями треда; 0 — сразу. |
data.link | string | null | |
data.media | array of object | Файлы поста по порядку. |
data.media[].id | string | null | Идентификатор в медиатеке; null, если файла там нет. |
data.media[].url | string | |
data.media[].alt | string | null | Описание файла для программ экранного доступа; передаётся в сети, которые его принимают (Bluesky, Mastodon, X, Pinterest). null, если описания нет. |
data.options | object | Параметры конкретной соцсети в том виде, в каком они были переданы. |
data.status | string | reconnect_wait — пост запланирован, но канал нужно переподключить. unknown — статус, который API не называет. One of draft, scheduled, reconnect_wait, published, failed, unknown.
|
data.publish_at | integer | null | Когда пост должен выйти (или должен был выйти); для черновика — null. Unix-секунды. |
data.retry_at | integer | null | Временная ошибка: очередь повторит попытку в это время. Unix-секунды. |
data.attempts | integer | Число попыток публикации на данный момент. |
data.result | PostResult | null | Что вернула последняя попытка публикации. |
data.result.message | string | null | Сообщение соцсети или followers.vc. |
data.result.error_class | string | null | transient, uncertain (пост мог быть опубликован), auth или permanent; null — ошибки нет. |
data.result.social_id | string | integer | null | Идентификатор поста в соцсети. |
data.result.thread | array of object | Что вышло с каждым сообщением треда, по порядку; публикация останавливается на первом отказе. |
data.result.thread[].social_id | string | null | Id сообщения в сети. |
data.result.thread[].url | string | null | Адрес сообщения, если сеть его даёт. |
data.result.thread[].error | string | null | Почему сеть отклонила сообщение или почему сеть не принимает тред. |
data.url | string | null | Адрес опубликованного поста, если соцсеть его возвращает. |
data.metrics | PostMetrics | null | Счётчики опубликованного поста. Счётчик, которого у сети нет, равен null: Bluesky, Mastodon и Reddit не считают просмотры, цитаты есть только у X, Bluesky, Mastodon и Threads. |
data.metrics.likes | integer | null | Лайки, избранное, реакции. |
data.metrics.reposts | integer | null | Репосты, ретвиты, бусты. |
data.metrics.comments | integer | null | Ответы и комментарии. |
data.metrics.quotes | integer | null | Репосты со своим текстом. |
data.metrics.views | integer | null | Просмотры или показы. |
data.metrics.updated_at | integer | Когда счётчики прочитаны. Unix-секунды. |
data.created_at | integer | null | Время создания. Unix-секунды. |
data.attempt_log | array of object | Все попытки публикации, сначала старые. |
data.attempt_log[].attempt | integer | |
data.attempt_log[].outcome | string | published, retry, failed или reconnect. |
data.attempt_log[].error_class | string | null | transient, uncertain, auth или permanent. |
data.attempt_log[].message | string | null | |
data.attempt_log[].at | integer | null | Время попытки. Unix-секунды. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 404 | not_found |
| 409 | not_retryable |
| 429 | rate_limited |
| 500 | server_error |
Для неудачного поста с ошибкой uncertain (запрос мог пройти, поэтому пост не повторяется): отмечает его опубликованным со ссылкой на запись в сети. Ссылка должна быть https, а для сети с известными адресами — на одном из них; id записи в сети берётся из ссылки, где она его содержит (result.social_id). В журнал попыток добавляется строка published, срабатывает вебхук post.published.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор поста. |
application/json| Поле | Тип | Описание |
|---|---|---|
url обязательно |
string (uri) | Ссылка https на запись в сети. At most 2000 characters. |
curl -X POST https://followers.vc/api/v1/posts/{id}/link \
-H "Authorization: Bearer $FOLLOWERS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://x.com/someone/status/1849000000000000001"}'
{
"data": {
"id": "kQ7x9a1b2c3d",
"channel": {
"id": "a8d3f0c2e1b94f7d",
"network": "telegram",
"name": "Acme news"
},
"type": "text",
"caption": "Hello from the API",
"thread": [
"string"
],
"thread_delay": 0,
"link": null,
"media": [
{
"id": "fil-5f1c0e2a9b",
"url": "https://followers.vc/storage/files/photo.png",
"alt": null
}
],
"options": {},
"status": "scheduled",
"publish_at": 1793000000,
"retry_at": 1793000000,
"attempts": 0,
"result": {
"message": "Bad token",
"error_class": "permanent",
"social_id": null,
"thread": [
{
"social_id": null,
"url": null,
"error": null
}
]
},
"url": null,
"metrics": {
"likes": 42,
"reposts": 7,
"comments": 5,
"quotes": 1,
"views": 1800,
"updated_at": 1793003600
},
"created_at": 1793000000,
"attempt_log": [
{
"attempt": 1,
"outcome": "retry",
"error_class": "transient",
"message": "Rate limit",
"at": 1793000000
}
]
}
}
| Поле | Тип | Описание |
|---|---|---|
data | PostDetail | |
data.id | string | Публичный идентификатор поста. |
data.channel | object | |
data.channel.id | string | null | Публичный идентификатор канала; null, если канал удалён. |
data.channel.network | string | null | |
data.channel.name | string | null | |
data.type | string | text, link или media (у более старых постов могут быть другие типы). |
data.caption | string | |
data.thread | array of string | Сообщения, вышедшие после поста: тред из ответов там, где у сети есть треды (X, Bluesky, Mastodon, Threads, Telegram, Discord, Slack), комментарии под постом там, где есть комментарии (Facebook, Instagram, Reddit, ВКонтакте). Пусто, если их нет. |
data.thread_delay | integer | Минуты между сообщениями треда; 0 — сразу. |
data.link | string | null | |
data.media | array of object | Файлы поста по порядку. |
data.media[].id | string | null | Идентификатор в медиатеке; null, если файла там нет. |
data.media[].url | string | |
data.media[].alt | string | null | Описание файла для программ экранного доступа; передаётся в сети, которые его принимают (Bluesky, Mastodon, X, Pinterest). null, если описания нет. |
data.options | object | Параметры конкретной соцсети в том виде, в каком они были переданы. |
data.status | string | reconnect_wait — пост запланирован, но канал нужно переподключить. unknown — статус, который API не называет. One of draft, scheduled, reconnect_wait, published, failed, unknown.
|
data.publish_at | integer | null | Когда пост должен выйти (или должен был выйти); для черновика — null. Unix-секунды. |
data.retry_at | integer | null | Временная ошибка: очередь повторит попытку в это время. Unix-секунды. |
data.attempts | integer | Число попыток публикации на данный момент. |
data.result | PostResult | null | Что вернула последняя попытка публикации. |
data.result.message | string | null | Сообщение соцсети или followers.vc. |
data.result.error_class | string | null | transient, uncertain (пост мог быть опубликован), auth или permanent; null — ошибки нет. |
data.result.social_id | string | integer | null | Идентификатор поста в соцсети. |
data.result.thread | array of object | Что вышло с каждым сообщением треда, по порядку; публикация останавливается на первом отказе. |
data.result.thread[].social_id | string | null | Id сообщения в сети. |
data.result.thread[].url | string | null | Адрес сообщения, если сеть его даёт. |
data.result.thread[].error | string | null | Почему сеть отклонила сообщение или почему сеть не принимает тред. |
data.url | string | null | Адрес опубликованного поста, если соцсеть его возвращает. |
data.metrics | PostMetrics | null | Счётчики опубликованного поста. Счётчик, которого у сети нет, равен null: Bluesky, Mastodon и Reddit не считают просмотры, цитаты есть только у X, Bluesky, Mastodon и Threads. |
data.metrics.likes | integer | null | Лайки, избранное, реакции. |
data.metrics.reposts | integer | null | Репосты, ретвиты, бусты. |
data.metrics.comments | integer | null | Ответы и комментарии. |
data.metrics.quotes | integer | null | Репосты со своим текстом. |
data.metrics.views | integer | null | Просмотры или показы. |
data.metrics.updated_at | integer | Когда счётчики прочитаны. Unix-секунды. |
data.created_at | integer | null | Время создания. Unix-секунды. |
data.attempt_log | array of object | Все попытки публикации, сначала старые. |
data.attempt_log[].attempt | integer | |
data.attempt_log[].outcome | string | published, retry, failed или reconnect. |
data.attempt_log[].error_class | string | null | transient, uncertain, auth или permanent. |
data.attempt_log[].message | string | null | |
data.attempt_log[].at | integer | null | Время попытки. Unix-секунды. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 404 | not_found |
| 409 | not_linkable |
| 422 | validation_failed |
| 429 | rate_limited |
| 500 | server_error |
Уведомления автора ключа.
Уведомления автора ключа — те же, что в колокольчике кабинета, — сначала новые.
| Имя | Где | Тип | Описание |
|---|---|---|---|
unread |
query | boolean | integer | 1 — только непрочитанные. One of true, false, 0, 1.
|
page |
query | integer | Страница, начиная с 1. Minimum 1. Default 1.
|
per_page |
query | integer | Число элементов на странице; значение больше 100 урезается до 100. Minimum 1. Default 25.
|
curl -X GET https://followers.vc/api/v1/notifications \ -H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": [
{
"id": "n-7d1e2f",
"message": "Your post on Acme news has been published.",
"url": null,
"source": "auto",
"type": "news",
"read": false,
"created_at": 1793000000
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 40
},
"links": {
"next": "https://followers.vc/api/v1/posts?per_page=25&page=2",
"prev": null
}
}
| Поле | Тип | Описание |
|---|---|---|
data | array of Notification | |
data[].id | string | Публичный идентификатор уведомления. |
data[].message | string | null | |
data[].url | string | null | Куда ведёт уведомление. |
data[].source | string | auto — отправлено followers.vc, manual — администраторами. One of auto, manual.
|
data[].type | string | null | |
data[].read | boolean | |
data[].created_at | integer | null | Время уведомления. Unix-секунды. |
meta | object | |
meta.page | integer | Текущая страница, начиная с 1. |
meta.per_page | integer | Элементов на странице (по умолчанию 25, не более 100). |
meta.total | integer | Элементов на всех страницах. |
links | object | |
links.next | string | null | Следующая страница; на последней — null. |
links.prev | string | null | Предыдущая страница; на первой — null. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, insufficient_scope |
| 422 | validation_failed |
| 429 | rate_limited |
| 500 | server_error |
Сколько уведомлений автора ключа не прочитано.
curl -X GET https://followers.vc/api/v1/notifications/unread-count \ -H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": {
"unread": 3
}
}
| Поле | Тип | Описание |
|---|---|---|
data | UnreadCount | |
data.unread | integer |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, insufficient_scope |
| 429 | rate_limited |
| 500 | server_error |
Можно повторять: уже прочитанное уведомление тоже отвечает 200.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор уведомления. |
curl -X POST https://followers.vc/api/v1/notifications/{id}/read \
-H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": {
"id": "n-7d1e2f",
"message": "Your post on Acme news has been published.",
"url": null,
"source": "auto",
"type": "news",
"read": false,
"created_at": 1793000000
}
}
| Поле | Тип | Описание |
|---|---|---|
data | Notification | |
data.id | string | Публичный идентификатор уведомления. |
data.message | string | null | |
data.url | string | null | Куда ведёт уведомление. |
data.source | string | auto — отправлено followers.vc, manual — администраторами. One of auto, manual.
|
data.type | string | null | |
data.read | boolean | |
data.created_at | integer | null | Время уведомления. Unix-секунды. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, insufficient_scope |
| 404 | not_found |
| 429 | rate_limited |
| 500 | server_error |
Отмечает прочитанными все уведомления автора ключа.
curl -X POST https://followers.vc/api/v1/notifications/read-all \ -H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": {
"unread": 3
}
}
| Поле | Тип | Описание |
|---|---|---|
data | UnreadCount | |
data.unread | integer |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, insufficient_scope |
| 429 | rate_limited |
| 500 | server_error |
Отчёты по каналам из их соцсетей.
Каналы команды, для которых есть отчёт, сгруппированные по соцсетям. Соцсети, не входящие в тариф, пропускаются.
curl -X GET https://followers.vc/api/v1/analytics \ -H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": [
{
"network": "facebook",
"name": "Facebook",
"channels": [
{
"id": "a8d3f0c2e1b94f7d",
"name": "Acme page",
"username": "acme",
"avatar": null
}
]
}
]
}
| Поле | Тип | Описание |
|---|---|---|
data | array of AnalyticsNetwork | |
data[].network | string | |
data[].name | string | |
data[].channels | array of object | |
data[].channels[].id | string | Публичный идентификатор канала. |
data[].channels[].name | string | null | |
data[].channels[].username | string | null | |
data[].channels[].avatar | string | null |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope |
| 429 | rate_limited |
| 500 | server_error |
Отчёт по одному каналу из того же сервиса соцсети, что и в кабинете. Соцсеть может опрашиваться прямо во время запроса, поэтому он может выполняться долго.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id обязательно |
path | string | Публичный идентификатор канала. |
from |
query | string | ISO 8601 или unix-секунды; дата без времени охватывает весь день. По умолчанию — за 30 дней до to. At most 64 characters.
|
to |
query | string | ISO 8601 или unix-секунды. По умолчанию — текущий момент. Диапазон — не более 365 дней. At most 64 characters. |
curl -X GET https://followers.vc/api/v1/analytics/{id} \
-H "Authorization: Bearer $FOLLOWERS_API_KEY"
{
"data": {
"followers": 120,
"chart": [
1,
2,
3
]
},
"meta": {
"channel": "a8d3f0c2e1b94f7d",
"from": "2026-09-16T00:00:00+00:00",
"to": "2026-10-16T09:30:00+00:00"
}
}
| Поле | Тип | Описание |
|---|---|---|
data | object | array | Отчёт в том виде, в каком его строит сервис соцсети (те же цифры, что показывает кабинет). Поля зависят от соцсети. |
meta | object | |
meta.channel | string | Публичный идентификатор канала. |
meta.from | string (date-time) | Начало диапазона, ISO 8601. |
meta.to | string (date-time) | Конец диапазона, ISO 8601. |
| Статус | Коды |
|---|---|
| 401 | unauthenticated |
| 403 | api_not_in_plan, plan_expired, module_not_in_plan, insufficient_scope, analytics_not_in_plan |
| 404 | not_found, analytics_unavailable |
| 422 | validation_failed |
| 429 | rate_limited |
| 500 | server_error |
| 502 | provider_error |
followers.vc отправляет подписанный POST с телом JSON на ваш адрес https, когда что-то происходит с вашими постами и каналами. Вебхуки настраиваются на странице «Вебхуки» в кабинете.
Пост запланирован на определённое время (одно событие на канал и время).
{
"id": "5b0c2a4e-1f0e-4c55-9a43-2d8c7e1b6f10",
"event": "post.scheduled",
"created_at": "2026-10-16T09:30:00Z",
"data": {
"post": {
"id": "kQ7x9a1",
"status": "scheduled",
"type": "media",
"time_post": "2026-10-16T09:30:00Z",
"url": null
},
"channel": {
"id": "a8d3f0c2",
"network": "telegram",
"name": "Example"
}
}
}
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор события: одинаковый при повторах и ручных повторных отправках — используйте его, чтобы пропускать дубликаты. |
event | "post.scheduled" | |
created_at | string (date-time) | Время события, ISO 8601 UTC. |
data | object | |
data.post | WebhookPost | |
data.post.id | string | Публичный идентификатор поста. |
data.post.status | string | One of scheduled, published, failed.
|
data.post.type | string | null | |
data.post.time_post | string | null (date-time) | Когда пост должен выйти, ISO 8601 UTC. |
data.post.url | string | null | Заполнено у опубликованного поста, если соцсеть возвращает ссылку. |
data.channel | WebhookChannel | |
data.channel.id | string | null | Публичный идентификатор канала. |
data.channel.network | string | null | |
data.channel.name | string | null |
| Заголовок | Описание |
|---|---|
X-Followers-Event | Событие, например post.published.
|
X-Followers-Delivery | Идентификатор этой доставки (при ручной повторной отправке новый). |
X-Followers-Timestamp | Unix-время подписи запроса. |
X-Followers-Signature | t=<timestamp>,v1=<signature>: HMAC-SHA256 в шестнадцатеричном виде от <timestamp>.<raw body> с секретом вебхука (whsec_…). Отклоняйте запрос, если t отличается от ваших часов более чем на 5 минут.
|
User-Agent | followers.vc-Webhooks/1.0.
|
| Ваш ответ | Что происходит |
|---|---|
| 2XX | Любой ответ 2xx в течение 10 секунд — доставлено. Тело ответа не используется. |
| default | Нет ответа, тайм-аут, 429 или 5xx — повтор через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов. Любой другой ответ 4xx или 3xx — сразу неудача. После 20 неудачных запросов подряд вебхук отключается. |
Пост опубликован в соцсети.
{
"id": "5b0c2a4e-1f0e-4c55-9a43-2d8c7e1b6f10",
"event": "post.published",
"created_at": "2026-10-16T09:30:00Z",
"data": {
"post": {
"id": "kQ7x9a1",
"status": "published",
"type": "media",
"time_post": "2026-10-16T09:30:00Z",
"url": "https://t.me/example/123"
},
"channel": {
"id": "a8d3f0c2",
"network": "telegram",
"name": "Example"
}
}
}
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор события: одинаковый при повторах и ручных повторных отправках — используйте его, чтобы пропускать дубликаты. |
event | "post.published" | |
created_at | string (date-time) | Время события, ISO 8601 UTC. |
data | object | |
data.post | WebhookPost | |
data.post.id | string | Публичный идентификатор поста. |
data.post.status | string | One of scheduled, published, failed.
|
data.post.type | string | null | |
data.post.time_post | string | null (date-time) | Когда пост должен выйти, ISO 8601 UTC. |
data.post.url | string | null | Заполнено у опубликованного поста, если соцсеть возвращает ссылку. |
data.channel | WebhookChannel | |
data.channel.id | string | null | Публичный идентификатор канала. |
data.channel.network | string | null | |
data.channel.name | string | null |
| Заголовок | Описание |
|---|---|
X-Followers-Event | Событие, например post.published.
|
X-Followers-Delivery | Идентификатор этой доставки (при ручной повторной отправке новый). |
X-Followers-Timestamp | Unix-время подписи запроса. |
X-Followers-Signature | t=<timestamp>,v1=<signature>: HMAC-SHA256 в шестнадцатеричном виде от <timestamp>.<raw body> с секретом вебхука (whsec_…). Отклоняйте запрос, если t отличается от ваших часов более чем на 5 минут.
|
User-Agent | followers.vc-Webhooks/1.0.
|
| Ваш ответ | Что происходит |
|---|---|
| 2XX | Любой ответ 2xx в течение 10 секунд — доставлено. Тело ответа не используется. |
| default | Нет ответа, тайм-аут, 429 или 5xx — повтор через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов. Любой другой ответ 4xx или 3xx — сразу неудача. После 20 неудачных запросов подряд вебхук отключается. |
Публикация поста окончательно не удалась: автоматических попыток больше не будет.
{
"id": "5b0c2a4e-1f0e-4c55-9a43-2d8c7e1b6f10",
"event": "post.failed",
"created_at": "2026-10-16T09:30:00Z",
"data": {
"post": {
"id": "kQ7x9a1",
"status": "failed",
"type": "media",
"time_post": "2026-10-16T09:30:00Z",
"url": null
},
"channel": {
"id": "a8d3f0c2",
"network": "telegram",
"name": "Example"
},
"error": {
"class": "permanent",
"message": "The token was revoked."
}
}
}
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор события: одинаковый при повторах и ручных повторных отправках — используйте его, чтобы пропускать дубликаты. |
event | "post.failed" | |
created_at | string (date-time) | Время события, ISO 8601 UTC. |
data | object | |
data.post | WebhookPost | |
data.post.id | string | Публичный идентификатор поста. |
data.post.status | string | One of scheduled, published, failed.
|
data.post.type | string | null | |
data.post.time_post | string | null (date-time) | Когда пост должен выйти, ISO 8601 UTC. |
data.post.url | string | null | Заполнено у опубликованного поста, если соцсеть возвращает ссылку. |
data.channel | WebhookChannel | |
data.channel.id | string | null | Публичный идентификатор канала. |
data.channel.network | string | null | |
data.channel.name | string | null | |
data.error | object | |
data.error.class | string | null | permanent, transient, uncertain (пост мог быть опубликован) или auth. One of permanent, transient, uncertain, auth.
|
data.error.message | string | null | Не более 500 символов. |
| Заголовок | Описание |
|---|---|
X-Followers-Event | Событие, например post.published.
|
X-Followers-Delivery | Идентификатор этой доставки (при ручной повторной отправке новый). |
X-Followers-Timestamp | Unix-время подписи запроса. |
X-Followers-Signature | t=<timestamp>,v1=<signature>: HMAC-SHA256 в шестнадцатеричном виде от <timestamp>.<raw body> с секретом вебхука (whsec_…). Отклоняйте запрос, если t отличается от ваших часов более чем на 5 минут.
|
User-Agent | followers.vc-Webhooks/1.0.
|
| Ваш ответ | Что происходит |
|---|---|
| 2XX | Любой ответ 2xx в течение 10 секунд — доставлено. Тело ответа не используется. |
| default | Нет ответа, тайм-аут, 429 или 5xx — повтор через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов. Любой другой ответ 4xx или 3xx — сразу неудача. После 20 неудачных запросов подряд вебхук отключается. |
Канал потерял доступ и требует переподключения; его посты ждут.
{
"id": "5b0c2a4e-1f0e-4c55-9a43-2d8c7e1b6f10",
"event": "channel.disconnected",
"created_at": "2026-10-16T09:30:00Z",
"data": {
"channel": {
"id": "a8d3f0c2",
"network": "telegram",
"name": "Example"
},
"reason": "The token was revoked."
}
}
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор события: одинаковый при повторах и ручных повторных отправках — используйте его, чтобы пропускать дубликаты. |
event | "channel.disconnected" | |
created_at | string (date-time) | Время события, ISO 8601 UTC. |
data | object | |
data.channel | WebhookChannel | |
data.channel.id | string | null | Публичный идентификатор канала. |
data.channel.network | string | null | |
data.channel.name | string | null | |
data.reason | string | Почему канал нужно переподключить; не более 500 символов. |
| Заголовок | Описание |
|---|---|
X-Followers-Event | Событие, например post.published.
|
X-Followers-Delivery | Идентификатор этой доставки (при ручной повторной отправке новый). |
X-Followers-Timestamp | Unix-время подписи запроса. |
X-Followers-Signature | t=<timestamp>,v1=<signature>: HMAC-SHA256 в шестнадцатеричном виде от <timestamp>.<raw body> с секретом вебхука (whsec_…). Отклоняйте запрос, если t отличается от ваших часов более чем на 5 минут.
|
User-Agent | followers.vc-Webhooks/1.0.
|
| Ваш ответ | Что происходит |
|---|---|
| 2XX | Любой ответ 2xx в течение 10 секунд — доставлено. Тело ответа не используется. |
| default | Нет ответа, тайм-аут, 429 или 5xx — повтор через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов. Любой другой ответ 4xx или 3xx — сразу неудача. После 20 неудачных запросов подряд вебхук отключается. |
Проверяющий одобрил посты на публичной странице согласования.
{
"id": "5b0c2a4e-1f0e-4c55-9a43-2d8c7e1b6f10",
"event": "approval.approved",
"created_at": "2026-10-16T09:30:00Z",
"data": {
"approval": {
"id": "Zp4r2Lx8",
"status": "approved",
"decided_by": "Anna",
"decided_at": "2026-10-16T09:30:00Z",
"comment": "Looks good."
},
"posts": [
{
"post": {
"id": "kQ7x9a1",
"status": "scheduled",
"type": "media",
"time_post": "2026-10-17T09:00:00Z",
"url": null
},
"channel": {
"id": "a8d3f0c2",
"network": "telegram",
"name": "Example"
}
}
]
}
}
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор события: одинаковый при повторах и ручных повторных отправках — используйте его, чтобы пропускать дубликаты. |
event | "approval.approved" | |
created_at | string (date-time) | Время события, ISO 8601 UTC. |
data | object | |
data.approval | object | Решение: одобрено. |
data.approval.id | string | Идентификатор запроса на согласование: одинаков для всех решений по нему. |
data.approval.status | "approved" | |
data.approval.decided_by | string | null | Имя, которое проверяющий указал на странице согласования. |
data.approval.decided_at | string | null (date-time) | Время решения, ISO 8601 UTC. |
data.approval.comment | string | null | Что проверяющий написал вместе с решением; не более 500 символов. |
data.posts | array of object | Посты запроса на согласование, по одному на канал. |
data.posts[].post | WebhookPost | |
data.posts[].post.id | string | Публичный идентификатор поста. |
data.posts[].post.status | string | One of scheduled, published, failed.
|
data.posts[].post.type | string | null | |
data.posts[].post.time_post | string | null (date-time) | Когда пост должен выйти, ISO 8601 UTC. |
data.posts[].post.url | string | null | Заполнено у опубликованного поста, если соцсеть возвращает ссылку. |
data.posts[].channel | WebhookChannel | |
data.posts[].channel.id | string | null | Публичный идентификатор канала. |
data.posts[].channel.network | string | null | |
data.posts[].channel.name | string | null | |
data.posts[].error | object | Только для поста, публикация которого не удалась. |
data.posts[].error.class | string | null | permanent, transient, uncertain (пост мог быть опубликован) или auth. One of permanent, transient, uncertain, auth.
|
data.posts[].error.message | string | null | Не более 500 символов. |
| Заголовок | Описание |
|---|---|
X-Followers-Event | Событие, например post.published.
|
X-Followers-Delivery | Идентификатор этой доставки (при ручной повторной отправке новый). |
X-Followers-Timestamp | Unix-время подписи запроса. |
X-Followers-Signature | t=<timestamp>,v1=<signature>: HMAC-SHA256 в шестнадцатеричном виде от <timestamp>.<raw body> с секретом вебхука (whsec_…). Отклоняйте запрос, если t отличается от ваших часов более чем на 5 минут.
|
User-Agent | followers.vc-Webhooks/1.0.
|
| Ваш ответ | Что происходит |
|---|---|
| 2XX | Любой ответ 2xx в течение 10 секунд — доставлено. Тело ответа не используется. |
| default | Нет ответа, тайм-аут, 429 или 5xx — повтор через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов. Любой другой ответ 4xx или 3xx — сразу неудача. После 20 неудачных запросов подряд вебхук отключается. |
Проверяющий запросил правки на публичной странице согласования; посты ждут нового согласования.
{
"id": "5b0c2a4e-1f0e-4c55-9a43-2d8c7e1b6f10",
"event": "approval.changes_requested",
"created_at": "2026-10-16T09:30:00Z",
"data": {
"approval": {
"id": "Zp4r2Lx8",
"status": "changes_requested",
"decided_by": "Anna",
"decided_at": "2026-10-16T09:30:00Z",
"comment": "Please shorten the text."
},
"posts": [
{
"post": {
"id": "kQ7x9a1",
"status": "scheduled",
"type": "media",
"time_post": "2026-10-17T09:00:00Z",
"url": null
},
"channel": {
"id": "a8d3f0c2",
"network": "telegram",
"name": "Example"
}
}
]
}
}
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор события: одинаковый при повторах и ручных повторных отправках — используйте его, чтобы пропускать дубликаты. |
event | "approval.changes_requested" | |
created_at | string (date-time) | Время события, ISO 8601 UTC. |
data | object | |
data.approval | object | Решение: запрошены правки. |
data.approval.id | string | Идентификатор запроса на согласование: одинаков для всех решений по нему. |
data.approval.status | "changes_requested" | |
data.approval.decided_by | string | null | Имя, которое проверяющий указал на странице согласования. |
data.approval.decided_at | string | null (date-time) | Время решения, ISO 8601 UTC. |
data.approval.comment | string | null | Что проверяющий написал вместе с решением; не более 500 символов. |
data.posts | array of object | Посты запроса на согласование, по одному на канал. |
data.posts[].post | WebhookPost | |
data.posts[].post.id | string | Публичный идентификатор поста. |
data.posts[].post.status | string | One of scheduled, published, failed.
|
data.posts[].post.type | string | null | |
data.posts[].post.time_post | string | null (date-time) | Когда пост должен выйти, ISO 8601 UTC. |
data.posts[].post.url | string | null | Заполнено у опубликованного поста, если соцсеть возвращает ссылку. |
data.posts[].channel | WebhookChannel | |
data.posts[].channel.id | string | null | Публичный идентификатор канала. |
data.posts[].channel.network | string | null | |
data.posts[].channel.name | string | null | |
data.posts[].error | object | Только для поста, публикация которого не удалась. |
data.posts[].error.class | string | null | permanent, transient, uncertain (пост мог быть опубликован) или auth. One of permanent, transient, uncertain, auth.
|
data.posts[].error.message | string | null | Не более 500 символов. |
| Заголовок | Описание |
|---|---|
X-Followers-Event | Событие, например post.published.
|
X-Followers-Delivery | Идентификатор этой доставки (при ручной повторной отправке новый). |
X-Followers-Timestamp | Unix-время подписи запроса. |
X-Followers-Signature | t=<timestamp>,v1=<signature>: HMAC-SHA256 в шестнадцатеричном виде от <timestamp>.<raw body> с секретом вебхука (whsec_…). Отклоняйте запрос, если t отличается от ваших часов более чем на 5 минут.
|
User-Agent | followers.vc-Webhooks/1.0.
|
| Ваш ответ | Что происходит |
|---|---|
| 2XX | Любой ответ 2xx в течение 10 секунд — доставлено. Тело ответа не используется. |
| default | Нет ответа, тайм-аут, 429 или 5xx — повтор через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов. Любой другой ответ 4xx или 3xx — сразу неудача. После 20 неудачных запросов подряд вебхук отключается. |
Отправляется только кнопкой Send test на странице Webhooks.
{
"id": "5b0c2a4e-1f0e-4c55-9a43-2d8c7e1b6f10",
"event": "ping",
"created_at": "2026-10-16T09:30:00Z",
"data": {
"webhook": {
"id": "Xh2k9",
"events": [
"post.published"
]
},
"message": "Test delivery from followers.vc"
}
}
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор события: одинаковый при повторах и ручных повторных отправках — используйте его, чтобы пропускать дубликаты. |
event | "ping" | |
created_at | string (date-time) | Время события, ISO 8601 UTC. |
data | object | |
data.webhook | object | |
data.webhook.id | string | |
data.webhook.events | array of string | |
data.message | string |
| Заголовок | Описание |
|---|---|
X-Followers-Event | Событие, например post.published.
|
X-Followers-Delivery | Идентификатор этой доставки (при ручной повторной отправке новый). |
X-Followers-Timestamp | Unix-время подписи запроса. |
X-Followers-Signature | t=<timestamp>,v1=<signature>: HMAC-SHA256 в шестнадцатеричном виде от <timestamp>.<raw body> с секретом вебхука (whsec_…). Отклоняйте запрос, если t отличается от ваших часов более чем на 5 минут.
|
User-Agent | followers.vc-Webhooks/1.0.
|
| Ваш ответ | Что происходит |
|---|---|
| 2XX | Любой ответ 2xx в течение 10 секунд — доставлено. Тело ответа не используется. |
| default | Нет ответа, тайм-аут, 429 или 5xx — повтор через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов. Любой другой ответ 4xx или 3xx — сразу неудача. После 20 неудачных запросов подряд вебхук отключается. |
© 2026, Все права защищены