SDK
Node.js SDK
Установите @sofia-post/node и вызывайте API Sofia из Node.js: публикуйте, планируйте, загружайте медиа и читайте статистику, с типизированными ошибками.
Официальный Node.js SDK оборачивает API Sofia v1 в один класс, Sofia: по одному методу на маршрут, типизированные запросы и ответы и один тип ошибки на любой сбой. У него нет зависимостей, и он работает на Node.js 20 и новее.
Установка
SDK опубликован в npm как пакет @sofia-post/node. Он работает и с import, и с require.
npm install @sofia-post/nodeБыстрый старт
Создайте клиент со своим ключом, найдите заведение, для которого нужно публиковать, затем запланируйте пост в Instagram:
import { Sofia } from '@sofia-post/node';
const sofia = new Sofia(process.env.SOFIA_API_KEY);
const [establishment] = await sofia.establishments();
const post = await sofia.post({
establishmentId: establishment.id,
targets: [{ platform: 'instagram', text: 'Tarte tatin du jour 🍎' }],
scheduledAt: new Date('2026-12-01T09:00:00Z'),
});
console.log(post.id, post.status);Аутентификация
Конструктор принимает личный ключ, созданный в Настройки › API, вкладка Личный доступ, или токен доступа OAuth, если ваше приложение действует от имени других пользователей Sofia (OAuth2). SDK отправляет его как Authorization: Bearer … в каждом запросе.
По умолчанию клиент обращается к https://api.sofia-post.com. Второй аргумент меняет базовый URL или fetch, которым отправляются запросы:
const sofia = new Sofia(apiKey, { baseUrl: 'http://localhost:3008' });Методы
| Метод | Что делает |
|---|---|
me() | От чьего имени действует ключ, его скоупы и является ли он тестовым |
platforms() | Правила каждой сети: длина текста, медиа, форматы |
establishments() | Заведения, до которых доходит ключ |
accounts(establishmentId) | Аккаунты соцсетей, подключённые к заведению |
connectionLink(establishmentId, input) | Страница на стороне Sofia, где человек подключает сеть |
insights(establishmentId, period?) | Охват, вовлечённость и подписчики по каждой сети |
post(input, options?) | Опубликовать сейчас, запланировать через scheduledAt или сохранить черновик |
postList(filters?) | Страница постов, отфильтрованных по заведению, статусу или датам |
getPost(id) · updatePost(id, input) · deletePost(id) | Прочитать, изменить или снять один пост |
upload(input) · uploadFromUrl(input) | Добавить файл или URL в медиатеку заведения |
mediaList(filters) · getMedia(id) · deleteMedia(id) | Просматривать и чистить медиатеку |
createWebhook(input) · webhooks() · deleteWebhook(id) · testWebhook(id) | Получать уведомления, когда пост вышел или не удался |
Посты
У поста по одной цели на каждую сеть, у каждой — свой текст, медиа и формат. Сначала проверьте его через dryRun: ничего не записывается, а отказ перечисляет каждое нарушенное правило. Передайте idempotencyKey, чтобы повторный запрос никогда не опубликовал пост дважды. scheduledAt принимает Date или строку ISO 8601.
import type { PublishPostInput } from '@sofia-post/node';
const input: PublishPostInput = {
establishmentId,
targets: [
{ platform: 'instagram', text: 'Menu du jour', media: [{ id: media.id }] },
{ platform: 'facebook', text: 'Menu du jour : tarte tatin et café' },
],
};
await sofia.post(input, { dryRun: true });
await sofia.post(input, { idempotencyKey: crypto.randomUUID() });Медиа
Загрузите файл один раз, затем укажите его id в media поста. upload принимает Buffer, Uint8Array или Blob и определяет тип содержимого по имени файла, если вы не задали contentType. uploadFromUrl позволяет Sofia скачать файл самой.
import { readFile } from 'node:fs/promises';
const media = await sofia.upload({
establishmentId,
file: await readFile('tarte-tatin.jpg'),
fileName: 'tarte-tatin.jpg',
});
const imported = await sofia.uploadFromUrl({ establishmentId, url: 'https://example.com/menu.png' });Пагинация
postList и mediaList отвечают по одной странице. Передайте nextCursor страницы как cursor следующего вызова; у последней страницы его нет.
let cursor: string | undefined;
do {
const page = await sofia.postList({ establishmentId, status: 'published', limit: 100, cursor });
page.data.forEach((post) => console.log(post.id));
cursor = page.nextCursor;
} while (cursor);Ошибки
Каждый метод выбрасывает SofiaApiError, когда API отклоняет вызов. Он содержит HTTP-status, стабильный code из конверта ошибки, details (для отклонённого поста — каждое нарушенное правило сети), requestId, который нужно сообщить в поддержку, и при 429 — retryAfterSeconds.
import { SofiaApiError } from '@sofia-post/node';
try {
await sofia.post(input);
} catch (error) {
if (!(error instanceof SofiaApiError)) throw error;
if (error.code === 'post_refused') console.log(error.details);
if (error.status === 429) await wait(error.retryAfterSeconds);
}Если API недоступен совсем, у ошибки код network_error и статус 0. SDK никогда не повторяет запрос сам: при 429 подождите retryAfterSeconds, а при 5xx увеличивайте паузы между попытками.
TypeScript
Пакет поставляется со своими типами: PublishPostInput, Post, Media, SocialAccount, EstablishmentInsights и другие экспортируются рядом с Sofia, так что редактор подсказывает каждое поле и каждое значение перечисления.
Дальше: справочник API описывает каждый маршрут, который вызывает SDK, а CLI управляет тем же API из терминала.