#Темы и действия стратега
Статус: Доступно (чтение) / Планируется (решения). Работают
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 (Платные операции). ПДн: нет.
#Базовый адрес
| Ячейка | Адрес |
|---|---|
| RU | https://formula-cream.pro/api/public/v1 |
| EN | https://wfacademy.org/api/public/v1 |
| ID | https://wfacademy.id/api/public/v1 |
Ошибки, пагинация, идемпотентность — Соглашения. Ключи и области — Аутентификация. Во всех примерах ключ заведомо ненастоящий.
#Два ресурса
| Ресурс | Откуда берётся | Что решается | Путь |
|---|---|---|---|
| Тема (элемент очереди тем) | Планировщик контента, разбор конкурентов, а также стратег после подтверждения его действия | Утвердить тему для статьи или отклонить | /strategist/topics |
| Действие (ожидающее действие чата) | Стратег в разговоре: «поставить тему в очередь» | Подтвердить: тема попадёт в очередь тем. Статья не пишется | /strategist/actions |
Путь темы от идеи до статьи состоит из трёх отдельных решений:
стратег в чате предлагает тему → вы подтверждаете действие → тема в очереди (proposed)
│
планировщик / конкуренты ──────────────────────────────────┘ (тоже кладут темы в очередь)
▼
вы утверждаете тему → тема approved
▼
вы заказываете генерацию → статья-черновик (draft)
Утверждение темы не запускает генерацию. Генерация — платная операция и запускается отдельным запросом: либо POST /articles/generation-jobs с topic_id (Генерация статей), либо тем же запросом утверждения с флагом generate: true; стоимость в обоих случаях явная — приходит в usage.
Видны только действия ваших собственных сессий разговора (Чат со стратегом); темы очереди общие для арендатора.
#Точки
Доступно — четыре маршрута чтения; четыре POST — Планируется.
| Метод и путь | Область | Что делает |
|---|---|---|
GET /strategist/topics | strategist:read | Очередь тем |
GET /strategist/topics/{id} | strategist:read | Одна тема |
POST /strategist/topics/{id}/approve | strategist:write | Утвердить тему; с generate: true — и заказать генерацию (платно) |
POST /strategist/topics/{id}/reject | strategist:write | Отклонить тему |
GET /strategist/actions | strategist:read | Ожидающие действия ваших сессий |
GET /strategist/actions/{id} | strategist:read | Одно действие |
POST /strategist/actions/{id}/confirm | strategist:write | Подтвердить действие |
POST /strategist/actions/{id}/cancel | strategist:write | Отклонить действие |
#Объект темы
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор |
title | string | null | Предлагаемый заголовок статьи |
rationale | string | null | Обоснование: почему тема нужна сейчас |
trend_score | number | null | Оценка важности, 0–100; по ней очередь упорядочена |
source | string | Откуда тема: planner, competitors, strategist и другие источники |
status | string | Состояние (таблица ниже) |
cluster_id | string (uuid) | null | Ядро ключевых слов, к которому относится тема |
generated_article_id | string (uuid) | null | Статья, созданная по теме |
proposed_at | string (ISO 8601) | Когда предложена |
decided_at | string (ISO 8601) | null | Когда принято решение |
expires_at | string (ISO 8601) | null | Когда необработанная тема устареет |
#Состояния темы (status)
| Состояние | Смысл |
|---|---|
proposed | Ждёт решения |
approved | Утверждена, статья ещё не заказана |
generating | Генерация статьи идёт |
generated | Статья-черновик создана, generated_article_id заполнен |
rejected | Отклонена вами |
duplicate | Признана повтором уже существующей темы |
expired | Срок вышел без решения |
Утвердить и отклонить можно только тему в состоянии proposed; в любом другом состоянии запрос получает 409 invalid_state.
#Как устроена очередь
- Очередь упорядочена по
trend_score, от большего к меньшему; темы без оценки (trend_score: null) идут в конце. - Необработанная тема живёт ограниченное время (
expires_at); после этого она показывается какexpired(состояние вычисляется при чтении, фильтрstatus=expiredучитывает его). - Тема, которую стратег предлагает в чате, не ставится в очередь повторно, если там уже есть тема с тем же заголовком (без учёта регистра и знаков): стратег отвечает «такая тема уже в очереди».
- Обоснование тем, взятых из разбора конкурентов, содержит чужие домены и заголовки. В v1 сырьё конкурентов (
competitors.raw) интеграции недоступно, поэтому такие упоминания изrationaleвырезаются так же, как в отчётах.
#GET /strategist/topics
| Параметр | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
status | string | нет | Одно из состояний темы | proposed |
limit | integer | нет | 1–100, по умолчанию 25 | 25 |
cursor | string | нет | Значение next_cursor; живёт 24 часа | |
include_deleted | boolean | нет | пока не поддерживается: 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) | да | Тема в состоянии proposed | 7a2c9e41-… |
generate | boolean | нет | По умолчанию false. true — сразу заказать генерацию статьи (платно); нужна область articles:generate и заполненный профиль редакции | false |
max_cost | object | нет | Только вместе с generate: true: потолок стоимости {"amount":"50.00","currency":"RUB"}; не выше предела стоимости одной операции арендатора | |
Заголовок Idempotency-Key | string | рекомендуется; обязателен при 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) | да | Тема в состоянии proposed | 7a2c9e41-… |
Заголовок Idempotency-Key | string | рекомендуется | До 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"
#Объект действия
Действие создаёт стратег в разговоре; исполняется оно только вашим запросом. Подтвердить своё предложение стратег не может никаким способом, ни прямо, ни «второй репликой от вашего имени».
| Поле | Тип | Описание |
|---|---|---|
id | string (uuid) | Идентификатор |
session_id | string (uuid) | null | Сессия разговора, в которой действие предложено |
type | string | Вид действия. В v1 — propose_topic (поставить тему в очередь) |
title | string | Что предлагается: для propose_topic — заголовок темы |
status | string | Состояние (таблица ниже) |
result | object | null | После исполнения: {topic_id} — тема, созданная в очереди |
error | object | null | При failed: {code, message} |
created_at | string (ISO 8601) | Когда предложено |
decided_at | string (ISO 8601) | null | Когда принято решение |
expires_at | string (ISO 8601) | null | Когда истекает: через 7 суток после предложения |
#Состояния действия (status)
| Состояние | Смысл |
|---|---|
proposed | Ждёт решения |
confirmed | Подтверждено, исполняется |
executed | Исполнено, result заполнен |
failed | Исполнение не удалось, причина в error |
cancelled | Отклонено вами |
expired | Срок (7 суток) вышел без решения |
Подтвердить и отклонить можно только действие в состоянии proposed. Повторное подтверждение, подтверждение просроченного или уже отклонённого действия даёт 409 invalid_state: в detail названо фактическое состояние. Права проверяются в момент подтверждения, а не в момент предложения: за 7 суток права могли измениться.
#GET /strategist/actions
Действия сессий вашей интеграции, новые сверху. Действия сотрудников в кабинете и других интеграций не видны. Пока чат стратега для интеграций не выпущен, список пуст.
| Параметр | Тип | Обязательно | Ограничения | Пример |
|---|---|---|---|---|
status | string | нет | Одно из состояний действия | proposed |
session_id | string (uuid) | нет | Только действия этой сессии | |
limit | integer | нет | 1–100, по умолчанию 25 | 25 |
cursor | string | нет | Значение 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) | да | Действие в состоянии proposed | d41e7a30-… |
Заголовок Idempotency-Key | string | рекомендуется | До 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_key | 401 | Ключ не принят |
scope_missing | 403 | Нет strategist:read / strategist:write, либо при generate: true нет articles:generate |
not_found | 404 | Темы или действия нет, либо они чужие |
invalid_state | 409 | Тема не в proposed (утверждение, отклонение); действие не в proposed (подтверждение, отмена), в том числе просроченное |
editorial_profile_required | 409 | generate: true, но не заполнен профиль редакции (Статьи) |
validation_failed | 422 | Не удаётся запустить генерацию: у темы нет ключевого слова; неверный max_cost |
insufficient_funds | 402 | Не хватает средств на генерацию |
spend_limit_reached | 402 | Сработал предел трат, параллелизма или стоимости операции |
upstream_unavailable | 503 | Нет ключа модели или модель недоступна |
rate_limited | 429 | Превышен предел частоты |
#Открытые вопросы
- Имя состояния темы (техническое). Во внутренней базе тема ждёт решения в состоянии
pending; публичное имяproposedвведено для согласованности с действиями и с правилом отклонения «только изproposed». Соответствие закрепляется при выпуске. - Отказ отклонения вне
proposed(техническое). Правило «только изproposed, иначе409 invalid_state» принято для публичного API; кабинет сегодня отклоняет тему без проверки состояния и должен быть исправлен так же. - Параметр
topic_idвPOST /articles/generation-jobs(техническое). Заказ генерации по утверждённой теме описан на странице Генерация статей; имя параметра предварительное. - Причина отказа (техническое). Машиночитаемая причина возврата темы в очередь при неудачном запуске генерации приходит кодом ошибки из каталога; полный набор причин (нет ключа модели, нет ключевого слова) уточнится в OpenAPI.
- Серия тем (техническое). В кабинете стратег умеет предлагать и серию тем; в v1 доступен только инструмент
propose_topic. Нужен ли вид действия «серия» публично, не решено. - Правка темы (к владельцу). Изменение заголовка или обоснования до утверждения проектом не предусмотрено. Нужно ли оно клиентам, решает владелец.