Обзор
Обзор API
Каждый endpoint Sofia API v1 с параметрами, scope и примерами, а также playground, чтобы проверить любой из них с вашим собственным ключом API.
Базовый URL https://api.sofia-post.com/api · Документ OpenAPI
Sofia API даёт вашему коду доступ к вашему аккаунту Sofia: заведениям ваших Workspaces, их подключённым сетям, медиатеке, публикациям и статистике. Это REST API по HTTPS; запросы и ответы — в JSON. Оно позволяет:
- Публиковать: сейчас, в назначенную дату или черновиком, в одну или несколько сетей сразу.
- Проверять перед публикацией: симуляция сообщает, приняла бы каждая сеть публикацию, ничего не записывая.
- Управлять медиа: загружайте файл или импортируйте его по URL в медиатеку заведения.
- Отслеживать публикации: читайте статус каждой сети, её постоянную ссылку или причину сбоя. Подписанный вебхук тоже может вас уведомить.
- Читать статистику: последние цифры, собранные Sofia по каждому подключённому аккаунту.
- Подключать сети: создайте размещённую ссылку, по которой владелец подключит недостающую сеть.
SDK и интеграции
- CLI `sofia`: CLI, опубликованный в npm под именем
sofia-post, для управления Sofia из терминала. - Сервер MCP: подключите Sofia к вашим ИИ-агентам.
- Zapier: интеграция Sofia подключается с вашим личным ключом. Она предлагает триггеры «Post Published» и «Post Failed», действие «Create Post» (опубликовать, запланировать или сохранить черновик) и поиск «Find Post». Она ещё не подана в публичный каталог Zapier.
Аутентификация
Каждый вызов несёт токен Bearer в заголовке Authorization. Sofia перечитывает связанные с ним права при каждом вызове: человек, удалённый из Workspace, или отозванное разрешение останавливает токен со следующего запроса.
Аутентифицироваться можно двумя способами:
| Способ | Для | Токен |
|---|---|---|
| Личный ключ | Автоматизации собственного аккаунта, скриптов, агентов | sofia_pk_live_…, действителен до истечения срока |
| OAuth, код авторизации + PKCE | Продукт, действующий от имени своих пользователей в выбранном ими Workspace | sofia_live_at_…, один час, обновляемый |
| OAuth, client credentials | Ваш сервер, автоматизирующий Workspace, владельцем которого вы являетесь | sofia_live_at_…, один час |
Ключ API
Если вы автоматизируете собственный Workspace, достаточно личного ключа. В приложении Sofia откройте Настройки › API, вкладку Личный доступ, и создайте ключ. Выберите его название, срок действия и разрешения. Ключ показывается один раз: скопируйте его и храните в хранилище секретов, но не в коде.
Создайте ключ в Настройки › API › Личный доступ. Он действует от вашего имени во всех ваших Workspaces: названное вами заведение определяет Workspace, а ваша роль в нём — что ключ может делать.
Передавайте ключ в заголовке Authorization как токен Bearer:
curl https://api.sofia-post.com/api/v1/establishments \
-H "Authorization: Bearer sofia_pk_live_…"`GET /v1/me` сообщает, от чьего имени действует ключ, его Workspaces и разрешения.
Вызов без нужного разрешения (scope) возвращает 403 insufficient_scope.
Токен OAuth2
Для SaaS-продукта, действующего от имени своих пользователей в их собственных Workspaces, зарегистрируйте клиента OAuth: каждый человек авторизует ваше приложение в выбранном им Workspace. Токены доступа OAuth начинаются с sofia_live_at_…, живут час и используются так же:
curl https://api.sofia-post.com/api/v1/establishments \
-H "Authorization: Bearer sofia_live_at_…"Создаёте приложение, к которому подключаются другие аккаунты Sofia? Его процесс authorization code с PKCE, client credentials и scopes описаны в OAuth2.
Базовый URL
Каждый endpoint находится под одним базовым URL:
https://api.sofia-post.com/api/v1Лимиты запросов
Sofia API считает запросы в трёх окнах: 15 минут, 24 часа и 30 дней.
| Токен | 15 минут | 24 часа | 30 дней |
|---|---|---|---|
| Личный ключ (на аккаунт) | 150 | 1 000 | 10 000 |
| Клиент OAuth | 100 | 500 | 15 000 |
| Клиент OAuth, на каждого авторизовавшего человека (до 1 000) | +10 | +25 | +100 |
Заголовки
Каждый ответ сообщает, сколько осталось, в формате IETF:
RateLimit: "15m";r=144;t=43, "24h";r=994;t=13543, "30d";r=9994;t=13543
RateLimit-Policy: "15m";q=150;w=900, "24h";q=1000;w=86400, "30d";q=10000;w=2592000r — число оставшихся запросов, t — секунды до сброса окна, q — квота, w — окно в секундах.
Когда окно заполнено
Ответ — 429 rate_limit_exceeded с заголовком Retry-After и исчерпанным окном в details. Подождите указанное время, прежде чем повторять.
Как эффективно использовать API
- Зарегистрируйте вебхук вместо того, чтобы в цикле опрашивать публикацию.
- Используйте
limitдо 100, чтобы читать списки за меньшее число страниц. - Кэшируйте то, что меняется редко: платформы, заведения, подключённые аккаунты.
Ошибки
Каждая ошибка возвращается со статусом HTTP и одним и тем же JSON-конвертом. code — контракт, который читает ваш код; message написано для человека и может меняться.
{
"error": {
"code": "invalid_request",
"message": "The request is invalid",
"details": [
{ "field": "targets[0].text", "code": "required", "message": "targets[0].text is required" }
],
"docUrl": "https://www.sofia-post.com/developers/errors#invalid_request",
"requestId": "2aab5943-6333-4caf-8478-25a85ebd4abf"
}
}detailsсообщает больше: отклонённое поле, причину сети, исчерпанное окно лимита.docUrlведёт к объяснению кода на этой странице.requestIdидентифицирует запрос: сообщите его в поддержку.
Коды ошибок
| Статус | Код | Значение |
|---|---|---|
| 400 | invalid_request | Тело, запрос или параметр недопустимы. Одна запись на каждое отклонённое поле: field, code (required, is_string, max…) и message. |
| 400 | invalid_json | Тело не является корректным JSON. |
| 400 | invalid_cursor | Курсор не был возвращён Sofia API. |
| 400 | webhook_url_invalid | Адрес webhook не https или ведёт на частный адрес. |
| 400 | webhook_event_invalid | Неизвестное событие webhook. |
| 400 | application_name_invalid | Настройки › API: название клиента отсутствует или слишком длинное. |
| 400 | application_profile_invalid | Настройки › API: одно из полей профиля клиента недопустимо; в подробностях указано, какое. |
| 400 | logo_invalid | Настройки › API: логотип отклонён (не менее 180×180 px, PNG, JPEG или WebP, не более 2 МБ). |
| 401 | invalid_token | Токен отсутствует, неизвестен, истёк или отозван, либо человек, стоящий за ним, потерял права, которые он несёт. См. заголовок WWW-Authenticate. |
| 401 | unauthorized | Требуется аутентификация. |
| 402 | subscription_required | Реальная запись (включая пробный запуск) требует активной подписки или пробного периода. |
| 403 | insufficient_scope | У токена нет scope эндпоинта; он указан в деталях. |
| 403 | insufficient_role | Ваша роль в этом Workspace не позволяет это действие. |
| 403 | live_mode_required | Реальная запись с тестовым токеном. |
| 403 | route_closed_to_applications | Этот эндпоинт недоступен приложениям. |
| 403 | forbidden | Доступ запрещён. |
| 403 | session_required | Настройки › API: для этого действия нужна сессия Sofia, а не токен. |
| 403 | impersonation_forbidden | Настройки › API: это действие отклоняется в заимствованной сессии. |
| 403 | workspace_owner_required | Это может сделать только владелец Workspace. |
| 403 | workspace_member_required | Вы не участник этого Workspace. |
| 404 | route_not_found | Ни один эндпоинт не отвечает на этот метод и путь. |
| 404 | establishment_not_found | Неизвестное заведение или недоступно этому токену. |
| 404 | media_not_found | Неизвестное медиа или недоступно этому токену. |
| 404 | post_not_found | Неизвестная публикация или недоступна этому токену. |
| 404 | webhook_not_found | Неизвестный webhook или принадлежит другому приложению. |
| 404 | not_found | Ресурс не существует. |
| 404 | application_not_found | Настройки › API: неизвестный клиент. |
| 404 | grant_not_found | Неизвестная авторизация или уже отозвана. |
| 404 | personal_key_not_found | Настройки › API: неизвестный личный ключ. |
| 404 | consent_not_found | Запрос авторизации истёк или не существует: начните заново с эндпоинта авторизации. |
| 405 | method_not_allowed | Этот метод не разрешён для этого эндпоинта. |
| 409 | post_in_flight | Сеть отправляет публикацию: повторите удаление чуть позже. |
| 409 | post_not_editable | Сеть уже отправила публикацию: её больше нельзя изменить. |
| 409 | account_already_connected | Эта сеть уже подключена к заведению. |
| 409 | webhook_limit_reached | У приложения уже 10 webhook. |
| 409 | idempotency_in_progress | Первый запрос с этим Idempotency-Key ещё выполняется. |
| 409 | conflict | Запрос противоречит текущему состоянию ресурса. |
| 409 | scope_removal_forbidden | Настройки › API: разрешение клиента можно добавить, но нельзя удалить. |
| 409 | public_client_has_no_secret | Настройки › API: у публичного клиента нет секрета. |
| 413 | payload_too_large | Файл превышает лимит загрузки. |
| 415 | unsupported_media_type | Неподдерживаемый тип содержимого. |
| 422 | post_refused | Сеть, заведение или ваш тариф отклонили бы публикацию. Одна запись на каждую причину, у каждой свой message. |
| 422 | media_import_refused | Не удалось импортировать медиа по этому URL; причина указана в деталях. |
| 422 | media_upload_refused | Загруженный файл не является поддерживаемым изображением или видео. |
| 422 | platform_not_offered | Эту сеть нельзя подключить. |
| 422 | idempotency_key_reused | Этот Idempotency-Key уже использовался с другим телом. |
| 422 | unprocessable_entity | Запрос понятен, но не может быть обработан. |
| 429 | rate_limit_exceeded | Окно лимита заполнено: подождите время из Retry-After. |
Причины отклонения публикации
post_refused перечисляет каждую причину в details[].code:
| Код | Значение |
|---|---|
unsupported_platform | Эта сеть не поддерживается. |
duplicate_platform | Одна и та же сеть указана дважды. |
unsupported_format | Эта сеть не предлагает этот формат. |
caption_too_long | Текст слишком длинный (max, actual). |
too_many_hashtags | Слишком много хештегов (max, actual). |
too_many_mentions | Слишком много упоминаний (max, actual). |
too_many_thread_posts | Тред слишком длинный (max, actual). |
too_many_media | Слишком много медиа (max, actual). |
media_mix_refused | В этой сети нельзя смешивать фото и видео. |
media_required | Этой сети нужно фото или видео. |
media_type_refused | Эта сеть не принимает этот тип медиа. |
scheduled_in_past | scheduledAt находится в прошлом. |
scheduled_at_invalid | Запланированной публикации нужен будущий scheduledAt. |
account_not_connected | Для этого заведения в этой сети нет подключённого аккаунта. |
account_ambiguous | В этой сети подключено несколько аккаунтов: укажите один через accountId. |
media_not_found | Медиа нет в медиатеке этого заведения. |
media_url_invalid | URL медиа должен быть публичным https-адресом. |
platform_unavailable | Эта сеть ещё не открыта в Sofia. |
publication_quota_exceeded | Квота публикаций вашего тарифа исчерпана. |
Обработка ошибок
- Стройте логику на
code, а не наmessage. - Повторяйте
429послеRetry-After, а5xx— с экспоненциальной задержкой. - Никогда не повторяйте
4xxбез изменений: сначала исправьте запрос. - Записывайте
requestIdпри каждой ошибке.
Пагинация
Списки (GET /v1/posts, GET /v1/media) возвращаются начиная с самых свежих, по одной странице. limit задаёт размер страницы от 1 до 100 (по умолчанию 20).
{
"data": [ … ],
"nextCursor": "WyIyMDI2LTEwLTI3VDE1OjExOjU5LjAxN1oiLCIx…"
}Передайте nextCursor как cursor, чтобы прочитать следующую страницу. Когда nextCursor отсутствует, вы достигли конца.
let cursor;
do {
const url = new URL('https://api.sofia-post.com/api/v1/posts');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const page = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${key}` } }).then((r) => r.json());
handle(page.data);
cursor = page.nextCursor;
} while (cursor);Курсор непрозрачен: никогда не составляйте и не изменяйте его. Курсор, который Sofia не возвращала, получает ответ 400 invalid_cursor.
Идемпотентность
Сетевая ошибка может оставить вас в сомнении, создана ли публикация. Отправьте заголовок Idempotency-Key с POST /v1/posts и POST /v1/media и повторите с тем же ключом: Sofia вернёт то, что ответила в первый раз, и ничего не создаст дважды.
curl -X POST https://api.sofia-post.com/api/v1/posts \
-H "Authorization: Bearer $SOFIA_API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d @post.json- Используйте новый случайный ключ (UUID) для каждой новой публикации и тот же ключ для каждой повторной попытки этой публикации.
- Тот же ключ с другим телом возвращает
422 idempotency_key_reused. - Пока первый запрос выполняется, тот же ключ возвращает
409 idempotency_in_progress: повторите чуть позже.
Тестовый режим и симуляция
Симуляция. Добавьте ?dry_run=true к POST /v1/posts: Sofia выполняет проверки настоящей публикации (правила каждой сети, подключённый аккаунт, медиа, дату и ваш тариф), ничего не сохраняя и не отправляя. Подробности ответов — на странице Публикация.
Тестовый режим. Приложение, созданное в режиме test (токены sofia_test_at_…), читает ваш настоящий Workspace, но никогда в него не пишет: его публикации симулируются и оцениваются по тем же правилам, а любая реальная запись возвращает 403 live_mode_required. Ключи и клиенты, созданные в Настройки › API, работают в режиме live; для проверки без публикации используйте симуляцию.
OAuth
/v1/oauth/authorizeНачать авторизациюОткройте в браузере. Неизвестный клиент или незарегистрированный redirect_uri даёт 400; иначе перенаправление на экран согласия Sofia.GET/.well-known/oauth-authorization-server/api/v1/oauthПолучить метаданные сервераВозвращает метаданные сервера авторизации OAuth 2.0 Sofia API (RFC 8414), чтобы клиент нашёл endpoint и поддерживаемые параметры.POST/v1/oauth/tokenВыдать токен доступаОбменивает client_credentials, authorization_code с PKCE или одноразовый refresh_token на непрозрачный токен на 3600 с для одного Workspace.Аккаунт
Платформы
Заведения
/v1/establishmentsСписок заведенийПеречисляет заведения Workspace токена. Используйте их идентификаторы как establishmentId для публикации, списка медиа и чтения статистики.GET/v1/establishments/{id}/accountsСписок подключённых аккаунтовПеречисляет сети, подключённые к заведению, без токенов и секретов сети, только то, что нужно, чтобы назвать аккаунт и выбрать его как accountId.POST/v1/establishments/{id}/connection-linksСоздать ссылку для подключенияВозвращает размещённую ссылку, где человек разрешает сети доступ к Sofia, и токен сети не попадает в ваше приложение. Действует 30 минут; только live.GET/v1/establishments/{id}/insightsПолучить статистику заведенияВозвращает последние действительные данные, собранные Sofia по каждому подключённому аккаунту. Ещё не собранный аккаунт показывает awaiting, но не ноль.Медиа
/v1/mediaСписок медиаПеречисляет медиатеку заведения, сначала новые, постранично, с URL, подписанными на текущее окно. Работает в тесте и в live.POST/v1/mediaИмпортировать или загрузить медиаДобавляет в медиатеку заведения файл (multipart) или URL https, который Sofia скачивает сама. Нужны live-приложение и активная подписка.GET/v1/media/{id}Получить медиаВозвращает медиа из медиатеки с подписанным URL на текущее окно, чтобы вы могли использовать его идентификатор в публикации.DELETE/v1/media/{id}Убрать медиаУбирает медиа из медиатеки его заведения, как это делает приложение. Файл не уничтожается, запланированная публикация сохраняет своё изображение.Публикации
/v1/postsСписок публикацийПеречисляет публикации заведений, доступных токену, сначала новые, с фильтрами по заведению, статусу, датам и автору, постранично.POST/v1/postsОпубликовать публикациюLive-приложения публикуют сразу, планируют по дате или сохраняют черновик; тестовые только имитируют. С ?dry_run=true всё проверяется, ничего не записывается.GET/v1/posts/{id}Получить публикациюВозвращает публикацию с состоянием каждой сети, ссылкой после публикации и ошибкой. В live любая публикация Workspace, в тесте имитированная.PATCH/v1/posts/{id}Изменить публикациюПока ни одна сеть не начала отправку, заменяет targets, задаёт scheduledAt или переводит публикацию между draft и scheduled. Пропущенное сохраняется.DELETE/v1/posts/{id}Удалить публикациюУдаляет публикацию, как это делает приложение: запланированное не отправится, а опубликованное снимается с сети. У тестовых приложений нечего удалять.Вебхуки
/v1/webhooksСписок вебхуковПеречисляет вебхуки приложения без их секрета подписи: секрет возвращается только в момент регистрации вебхука, повторно получить его нельзя.POST/v1/webhooksЗарегистрировать вебхукРегистрирует URL https, который Sofia вызывает с подписанным JSON-событием, до пяти повторов при сбое. Секрет виден один раз; не больше 10 вебхуков.DELETE/v1/webhooks/{id}Удалить вебхукУдаляет вебхук. Он больше ничего не получает, даже доставки, ожидавшие повтора, а вызов не возвращает тела ответа, только код 204.POST/v1/webhooks/{id}/testОтправить тестовое событиеДоставляет подписанное событие webhook.test как любое другое, не дожидаясь получателя, чтобы вы проверили подпись. Не более пяти в минуту.