◆ Sapport для разработчиков Справочник API Каталог ошибок OpenAPI
Разделы

#Темы и действия стратега

Статус: Доступно (чтение) / Планируется (решения). Работают GET /strategist/topics, GET /strategist/topics/{id}, GET /strategist/actions и GET /strategist/actions/{id} (область strategist:read). Утверждение, отклонение и подтверждение (POST …) — Планируется: вызовы вернут 404.

Стратег не публикует и не тратит деньги без вашего подтверждения: он готовит предложение, а решение принимаете вы. В API таких предложений два вида, и это два разных ресурса.

Доступ: strategist:read — чтение; strategist:write — утверждение, отклонение и подтверждение. Платно: утверждение темы бесплатно; генерация статьи по теме — платная операция с областью articles:generate (Платные операции). ПДн: нет.

#Базовый адрес

ЯчейкаАдрес
RUhttps://formula-cream.pro/api/public/v1
ENhttps://wfacademy.org/api/public/v1
IDhttps://wfacademy.id/api/public/v1

Ошибки, пагинация, идемпотентность — Соглашения. Ключи и области — Аутентификация. Во всех примерах ключ заведомо ненастоящий.

#Два ресурса

РесурсОткуда берётсяЧто решаетсяПуть
Тема (элемент очереди тем)Планировщик контента, разбор конкурентов, а также стратег после подтверждения его действияУтвердить тему для статьи или отклонить/strategist/topics
Действие (ожидающее действие чата)Стратег в разговоре: «поставить тему в очередь»Подтвердить: тема попадёт в очередь тем. Статья не пишется/strategist/actions

Путь темы от идеи до статьи состоит из трёх отдельных решений:

стратег в чате предлагает тему   →  вы подтверждаете действие  →  тема в очереди (proposed)
                                                                   │
         планировщик / конкуренты ──────────────────────────────────┘ (тоже кладут темы в очередь)
                                                                   ▼
                                           вы утверждаете тему  →  тема approved
                                                                   ▼
                      вы заказываете генерацию  →  статья-черновик (draft)

Утверждение темы не запускает генерацию. Генерация — платная операция и запускается отдельным запросом: либо POST /articles/generation-jobs с topic_id (Генерация статей), либо тем же запросом утверждения с флагом generate: true; стоимость в обоих случаях явная — приходит в usage.

Видны только действия ваших собственных сессий разговора (Чат со стратегом); темы очереди общие для арендатора.

#Точки

Доступно — четыре маршрута чтения; четыре POST — Планируется.

Метод и путьОбластьЧто делает
GET /strategist/topicsstrategist:readОчередь тем
GET /strategist/topics/{id}strategist:readОдна тема
POST /strategist/topics/{id}/approvestrategist:writeУтвердить тему; с generate: true — и заказать генерацию (платно)
POST /strategist/topics/{id}/rejectstrategist:writeОтклонить тему
GET /strategist/actionsstrategist:readОжидающие действия ваших сессий
GET /strategist/actions/{id}strategist:readОдно действие
POST /strategist/actions/{id}/confirmstrategist:writeПодтвердить действие
POST /strategist/actions/{id}/cancelstrategist:writeОтклонить действие

#Объект темы

ПолеТипОписание
idstring (uuid)Идентификатор
titlestring | nullПредлагаемый заголовок статьи
rationalestring | nullОбоснование: почему тема нужна сейчас
trend_scorenumber | nullОценка важности, 0–100; по ней очередь упорядочена
sourcestringОткуда тема: planner, competitors, strategist и другие источники
statusstringСостояние (таблица ниже)
cluster_idstring (uuid) | nullЯдро ключевых слов, к которому относится тема
generated_article_idstring (uuid) | nullСтатья, созданная по теме
proposed_atstring (ISO 8601)Когда предложена
decided_atstring (ISO 8601) | nullКогда принято решение
expires_atstring (ISO 8601) | nullКогда необработанная тема устареет

#Состояния темы (status)

СостояниеСмысл
proposedЖдёт решения
approvedУтверждена, статья ещё не заказана
generatingГенерация статьи идёт
generatedСтатья-черновик создана, generated_article_id заполнен
rejectedОтклонена вами
duplicateПризнана повтором уже существующей темы
expiredСрок вышел без решения

Утвердить и отклонить можно только тему в состоянии proposed; в любом другом состоянии запрос получает 409 invalid_state.

#Как устроена очередь

#GET /strategist/topics

ПараметрТипОбязательноОграниченияПример
statusstringнетОдно из состояний темыproposed
limitintegerнет1–100, по умолчанию 2525
cursorstringнетЗначение next_cursor; живёт 24 часа
include_deletedbooleanнетпока не поддерживается: true даёт 422 not_supported (удалённые темы не хранятся)false
{
  "data": [
    {
      "id": "7a2c9e41-3b58-4d06-9f17-c8e5b1a02d63",
      "title": "Как выбрать увлажняющий крем для сухой кожи",
      "rationale": "Запрос растёт: 250 показов в сутки, статьи по теме на сайте нет.",
      "trend_score": 78.5,
      "source": "planner",
      "status": "proposed",
      "cluster_id": null,
      "generated_article_id": null,
      "proposed_at": "2026-10-10T07:00:00Z",
      "decided_at": null,
      "expires_at": "2026-10-24T07:00:00Z"
    }
  ],
  "next_cursor": null
}
curl "https://formula-cream.pro/api/public/v1/strategist/topics?status=proposed" \
  -H "Authorization: Bearer $SAPPORT_API_KEY"
# SAPPORT_API_KEY=sap_ru_test_EXAMPLE0KEYID_… (пример, не настоящий ключ)
const res = await fetch(
  "https://formula-cream.pro/api/public/v1/strategist/topics?status=proposed",
  { headers: { Authorization: `Bearer ${process.env.SAPPORT_API_KEY}` } },
);
const { data } = await res.json();
r = httpx.get(
    "https://formula-cream.pro/api/public/v1/strategist/topics",
    params={"status": "proposed"},
    headers={"Authorization": f"Bearer {os.environ['SAPPORT_API_KEY']}"},
)
topics = r.json()["data"]
$ch = curl_init("https://formula-cream.pro/api/public/v1/strategist/topics?status=proposed");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SAPPORT_API_KEY")],
]);
$topics = json_decode(curl_exec($ch), true)["data"];

#GET /strategist/topics/{id}

ПараметрТипОбязательноОграниченияПример
id (путь)string (uuid)даТема вашего арендатора7a2c9e41-…

Ответ 200 — объект темы. Чужая или несуществующая тема — 404 not_found.

#POST /strategist/topics/{id}/approve

Утверждает тему: она переходит в approved. Статья при этом не пишется и деньги не списываются.

ПараметрТипОбязательноОграниченияПример
id (путь)string (uuid)даТема в состоянии proposed7a2c9e41-…
generatebooleanнетПо умолчанию false. true — сразу заказать генерацию статьи (платно); нужна область articles:generate и заполненный профиль редакцииfalse
max_costobjectнетТолько вместе с generate: true: потолок стоимости {"amount":"50.00","currency":"RUB"}; не выше предела стоимости одной операции арендатора
Заголовок Idempotency-Keystringрекомендуется; обязателен при generate: trueДо 255 символов5c1f8d2e-…

Без generate ответ 200 OK:

{
  "id": "7a2c9e41-3b58-4d06-9f17-c8e5b1a02d63",
  "status": "approved",
  "decided_at": "2026-10-11T10:30:00Z"
}

С generate: true ответ 202 Accepted — конверт operation вида article.generation (Асинхронные операции); тема переходит в generating:

{
  "operation": {
    "id": "op_EXAMPLE02",
    "type": "article.generation",
    "status": "queued",
    "created_at": "2026-10-11T10:30:00Z",
    "resource": { "type": "generation_job", "id": "e4c1a7b2-5d3f-4a90-b8c6-1f2e3d4c5b6a" }
  }
}

Результат — как у любой генерации: ?wait≤20 на этом же запросе, GET /operations/{id}, GET /articles/generation-jobs/{id} либо события operation.completed и article.ready (Генерация статей). Стоимость придёт в operation.usage. Если генерацию запустить не удалось (нет профиля, не хватает средств, предел трат), тема остаётся в состоянии approved, а ошибка названа кодом из каталога.

Решение принимается один раз: если два запроса пришли одновременно, один выполнится, второй получит 409 invalid_state; платная генерация не запускается дважды.

curl -X POST "https://formula-cream.pro/api/public/v1/strategist/topics/$TOPIC_ID/approve" \
  -H "Authorization: Bearer $SAPPORT_API_KEY" \
  -H "Idempotency-Key: 5c1f8d2e-9a47-4b30-8e61-d3a5c7f20b94" \
  -H "Content-Type: application/json" \
  -d '{"generate":true,"max_cost":{"amount":"50.00","currency":"RUB"}}'
const res = await fetch(
  `https://formula-cream.pro/api/public/v1/strategist/topics/${topicId}/approve?wait=20`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SAPPORT_API_KEY}`,
      "Idempotency-Key": crypto.randomUUID(),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ generate: true }),
  },
);
if (res.status === 202) {
  const { operation } = await res.json();
}
r = httpx.post(
    f"https://formula-cream.pro/api/public/v1/strategist/topics/{topic_id}/approve",
    params={"wait": 20},
    headers={
        "Authorization": f"Bearer {os.environ['SAPPORT_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"generate": True},
    timeout=30,
)
$ch = curl_init("https://formula-cream.pro/api/public/v1/strategist/topics/" . $topicId . "/approve?wait=20");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("SAPPORT_API_KEY"),
        "Idempotency-Key: " . bin2hex(random_bytes(16)),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode(["generate" => true]),
]);
$res = curl_exec($ch);

#POST /strategist/topics/{id}/reject

Отклоняет тему. Бесплатно. Решение запоминается (decided_at), тема больше не предлагается к утверждению.

ПараметрТипОбязательноОграниченияПример
id (путь)string (uuid)даТема в состоянии proposed7a2c9e41-…
Заголовок Idempotency-KeystringрекомендуетсяДо 255 символов9b2e4c70-…

Тело не нужно. Ответ 200 OK:

{
  "id": "7a2c9e41-3b58-4d06-9f17-c8e5b1a02d63",
  "status": "rejected",
  "decided_at": "2026-10-11T10:31:00Z"
}

Отклонить можно только тему в состоянии proposed. Для темы в любом другом состоянии (в том числе уже отклонённой или с готовой статьёй) ответ — 409 invalid_state; состояние темы не меняется.

curl -X POST "https://formula-cream.pro/api/public/v1/strategist/topics/$TOPIC_ID/reject" \
  -H "Authorization: Bearer $SAPPORT_API_KEY" \
  -H "Idempotency-Key: 9b2e4c70-1d3a-4f58-a6e2-7c0b9d4e1f35"

#Объект действия

Действие создаёт стратег в разговоре; исполняется оно только вашим запросом. Подтвердить своё предложение стратег не может никаким способом, ни прямо, ни «второй репликой от вашего имени».

ПолеТипОписание
idstring (uuid)Идентификатор
session_idstring (uuid) | nullСессия разговора, в которой действие предложено
typestringВид действия. В v1 — propose_topic (поставить тему в очередь)
titlestringЧто предлагается: для propose_topic — заголовок темы
statusstringСостояние (таблица ниже)
resultobject | nullПосле исполнения: {topic_id} — тема, созданная в очереди
errorobject | nullПри failed: {code, message}
created_atstring (ISO 8601)Когда предложено
decided_atstring (ISO 8601) | nullКогда принято решение
expires_atstring (ISO 8601) | nullКогда истекает: через 7 суток после предложения

#Состояния действия (status)

СостояниеСмысл
proposedЖдёт решения
confirmedПодтверждено, исполняется
executedИсполнено, result заполнен
failedИсполнение не удалось, причина в error
cancelledОтклонено вами
expiredСрок (7 суток) вышел без решения

Подтвердить и отклонить можно только действие в состоянии proposed. Повторное подтверждение, подтверждение просроченного или уже отклонённого действия даёт 409 invalid_state: в detail названо фактическое состояние. Права проверяются в момент подтверждения, а не в момент предложения: за 7 суток права могли измениться.

#GET /strategist/actions

Действия сессий вашей интеграции, новые сверху. Действия сотрудников в кабинете и других интеграций не видны. Пока чат стратега для интеграций не выпущен, список пуст.

ПараметрТипОбязательноОграниченияПример
statusstringнетОдно из состояний действияproposed
session_idstring (uuid)нетТолько действия этой сессии
limitintegerнет1–100, по умолчанию 2525
cursorstringнетЗначение next_cursor; живёт 24 часа
{
  "data": [
    {
      "id": "d41e7a30-8b25-4c69-a0f3-52c9e1b7d806",
      "session_id": "c2e84a17-9d03-4f5b-a6c8-3b71e0d92f45",
      "type": "propose_topic",
      "title": "Как выбрать увлажняющий крем для сухой кожи",
      "status": "proposed",
      "result": null,
      "error": null,
      "created_at": "2026-10-11T10:02:11Z",
      "decided_at": null,
      "expires_at": "2026-10-18T10:02:11Z"
    }
  ],
  "next_cursor": null
}
curl "https://formula-cream.pro/api/public/v1/strategist/actions?status=proposed" \
  -H "Authorization: Bearer $SAPPORT_API_KEY"

#GET /strategist/actions/{id}

ПараметрТипОбязательноОграниченияПример
id (путь)string (uuid)даДействие вашей интеграцииd41e7a30-…

Ответ 200 — объект действия. Действие чужой интеграции, чужого арендатора или несуществующее — 404 not_found.

#POST /strategist/actions/{id}/confirm

Подтверждает действие и исполняет его: для propose_topic в очереди появляется тема в состоянии proposed, result.topic_id указывает на неё. Бесплатно; статья не пишется, тему нужно утвердить отдельно.

ПараметрТипОбязательноОграниченияПример
id (путь)string (uuid)даДействие в состоянии proposedd41e7a30-…
Заголовок Idempotency-KeystringрекомендуетсяДо 255 символов3f6a9c12-…

Тело не нужно. Ответ 200 OK — объект действия:

{
  "id": "d41e7a30-8b25-4c69-a0f3-52c9e1b7d806",
  "session_id": "c2e84a17-9d03-4f5b-a6c8-3b71e0d92f45",
  "type": "propose_topic",
  "title": "Как выбрать увлажняющий крем для сухой кожи",
  "status": "executed",
  "result": { "topic_id": "7a2c9e41-3b58-4d06-9f17-c8e5b1a02d63" },
  "error": null,
  "created_at": "2026-10-11T10:02:11Z",
  "decided_at": "2026-10-11T10:40:02Z",
  "expires_at": "2026-10-18T10:02:11Z"
}
curl -X POST "https://formula-cream.pro/api/public/v1/strategist/actions/$ACTION_ID/confirm" \
  -H "Authorization: Bearer $SAPPORT_API_KEY" \
  -H "Idempotency-Key: 3f6a9c12-7e50-4b84-9d21-a8c4e0b6f593"
const res = await fetch(
  `https://formula-cream.pro/api/public/v1/strategist/actions/${actionId}/confirm`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SAPPORT_API_KEY}`,
      "Idempotency-Key": crypto.randomUUID(),
    },
  },
);
const action = await res.json();
r = httpx.post(
    f"https://formula-cream.pro/api/public/v1/strategist/actions/{action_id}/confirm",
    headers={
        "Authorization": f"Bearer {os.environ['SAPPORT_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
)
action = r.json()
$ch = curl_init("https://formula-cream.pro/api/public/v1/strategist/actions/" . $actionId . "/confirm");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("SAPPORT_API_KEY"),
        "Idempotency-Key: " . bin2hex(random_bytes(16)),
    ],
]);
$action = json_decode(curl_exec($ch), true);

#POST /strategist/actions/{id}/cancel

Отклоняет действие: оно переходит в cancelled, тема в очередь не попадает. Бесплатно. Параметры и ограничения как у confirm; ответ 200 OK — объект действия со статусом cancelled.

curl -X POST "https://formula-cream.pro/api/public/v1/strategist/actions/$ACTION_ID/cancel" \
  -H "Authorization: Bearer $SAPPORT_API_KEY" \
  -H "Idempotency-Key: 6d0b3e85-2a71-4c96-b4f8-1e9a5c7d3028"

#События

СобытиеКогда
operation.completedЗавершилась генерация, заказанная утверждением темы с generate: true
article.readyПо теме создана статья-черновик
article.generation.failedГенерация по теме не удалась

Отдельных событий «новая тема» и «действие подтверждено» нет. Подпись, повторы, проверка — Вебхуки.

#Ошибки

Общий формат и каталог кодов — Соглашения. Для этих точек:

КодHTTPКогда
invalid_api_key401Ключ не принят
scope_missing403Нет strategist:read / strategist:write, либо при generate: true нет articles:generate
not_found404Темы или действия нет, либо они чужие
invalid_state409Тема не в proposed (утверждение, отклонение); действие не в proposed (подтверждение, отмена), в том числе просроченное
editorial_profile_required409generate: true, но не заполнен профиль редакции (Статьи)
validation_failed422Не удаётся запустить генерацию: у темы нет ключевого слова; неверный max_cost
insufficient_funds402Не хватает средств на генерацию
spend_limit_reached402Сработал предел трат, параллелизма или стоимости операции
upstream_unavailable503Нет ключа модели или модель недоступна
rate_limited429Превышен предел частоты

#Открытые вопросы