/v1/workflows — тот же граф canvas-редактора и та же отправка на исполнение, которыми пользуется сам редактор в студии: одна реализация на оба входа, без второй копии логики. Каждый узел цепочки списывает кредиты через тот же общий конвейер отправки, что и POST /v1/generations — модерация промпта, проверка владения референсами и модерация изображений идут в том же порядке.
Маршруты доступны ключу или OAuth-токену ровно как остальной /v1 — см. аутентификацию, — а также сессионной куке, которой пользуется сам редактор в браузере. Сверх этого канал ограничен ролью аккаунта: маршруты принимают её только для ADMIN с включённым флагом возможности; остальным ролям, включая SUPPORT, каждый маршрут под этим префиксом отвечает 404 с кодом not_found — неотличимо от несуществующего маршрута.
Чтение требует область workflows:read, запись — workflows:write; словарь областей и как их узнать у своего ключа — на странице аутентификации. Ни один маршрут здесь не проверяет оплаченность аккаунта отдельно: деньги списывает только запуск, и он списывает их через тот же платёжный узел, что и обычная генерация.
Чертёж графа (canvas) переносится как есть: конверт вокруг него (id, title, expected_revision) — snake_case, как и любое тело /v1, а поля внутри самого canvas называются так же, как их пишет редактор студии, без преобразования.
Сохранённые цепочки
GET /v1/workflows/templates перечисляет активные цепочки аккаунта, новые по обновлению первыми. Их не больше 50 одновременно, список не постраничный, и next_cursor в ответе всегда null. POST /v1/workflows/templates сохраняет новую цепочку под заголовком: тело несёт title и canvas, необязательный id задаёт его заранее. Референс внутри графа (загруженное изображение или аудио), которым вызывающий не владеет, отклоняется как 400 invalid_request; потолок в 50 цепочек или id, уже занятый записью в корзине, — как 409 resource_conflict.
curl -s https://api.coremind.ru/v1/workflows/templates \
-H "Authorization: Bearer cmk_..."
curl -s -X POST https://api.coremind.ru/v1/workflows/templates \
-H "Authorization: Bearer cmk_..." \
-H "Content-Type: application/json" \
-d '{"title": "...", "canvas": {"nodes": [...], "edges": [...]}}'GET /v1/workflows/templates/{id} отдаёт сохранённый документ целиком. Чужой или несуществующий id отвечает 404 not_found одинаково в обоих случаях — владение не подтверждается и не опровергается отдельно. DELETE /v1/workflows/templates/{id} — мягкое удаление: цепочка, её версии и история промптов остаются восстановимыми через POST /v1/workflows/templates/{id}/restore до тех пор, пока запись не удалена из корзины насовсем.
PATCH /v1/workflows/templates/{id} переименовывает цепочку, заменяет её граф или то и другое разом. Заголовок несёт expected_revision обязательно: значение должно совпадать с текущей revision записи, иначе запись отклоняется как 409 revision_conflict вместо того, чтобы молча перезаписать правку из другой вкладки или устройства. Запись, которая ничего фактически не меняет, всё равно отвечает 200, не увеличивая revision.
curl -s -X PATCH https://api.coremind.ru/v1/workflows/templates/<id> \
-H "Authorization: Bearer cmk_..." \
-H "Content-Type: application/json" \
-d '{"expected_revision": 3, "title": "...", "canvas": {"nodes": [...], "edges": [...]}}'GET /v1/workflows/templates/{id}/versions без ?revision= отдаёт лёгкий список сохранённых ревизий, новые первыми, не больше 50; с ним — одну ревизию целиком, вместе с графом. GET /v1/workflows/templates/{id}/prompt-history с обязательным ?node_id= отдаёт историю текста одного промпт-узла вместе с привязкой его переменных, новые версии первыми; ?before= листает её дальше назад по ревизии, а next_before в ответе — значение для следующего вызова либо null, когда история исчерпана.
GET /v1/workflows/templates/trash перечисляет архивированные цепочки, новые удалённые первыми, в курсорном конверте: следующую страницу открывает ?cursor=, размер страницы фиксирован сервером. DELETE /v1/workflows/templates/trash?id={id} удаляет запись из корзины насовсем вместе с её версиями и историей промптов; годится только id, уже лежащий в корзине — активная цепочка отвечает тем же 404, что и чужая или несуществующая. POST /v1/workflows/templates/{id}/restore отменяет удаление и отвечает 409 resource_conflict, если аккаунт уже держит 50 активных цепочек — тогда восстановлению нужно сначала освободить место.
curl -s https://api.coremind.ru/v1/workflows/templates/trash \
-H "Authorization: Bearer cmk_..."
curl -s -X POST https://api.coremind.ru/v1/workflows/templates/<id>/restore \
-H "Authorization: Bearer cmk_..."
curl -s -X DELETE "https://api.coremind.ru/v1/workflows/templates/trash?id=<id>" \
-H "Authorization: Bearer cmk_..."Блоки
Блок — сохранённый подграф: один или несколько связанных узлов, выделенных из цепочки, под своим заголовком. GET /v1/workflows/blocks перечисляет блоки аккаунта, новые по обновлению первыми, не больше 100 одновременно, next_cursor всегда null. POST /v1/workflows/blocks сохраняет новый блок — те же правила владения референсами и тот же 409 resource_conflict на переполнении, но против отдельного потолка блока в 100 записей.
curl -s https://api.coremind.ru/v1/workflows/blocks \
-H "Authorization: Bearer cmk_..."
curl -s -X POST https://api.coremind.ru/v1/workflows/blocks \
-H "Authorization: Bearer cmk_..." \
-H "Content-Type: application/json" \
-d '{"title": "...", "canvas": {"nodes": [...], "edges": [...]}}'GET /v1/workflows/blocks/{id} без ?revision= отдаёт текущий блок вместе с лёгким списком его ревизий; с ним — ту одну историческую ревизию целиком, без списка рядом. PATCH /v1/workflows/blocks/{id} подчиняется тому же правилу оптимистичной конкурентности, что и цепочки: expected_revision обязателен и должен совпасть с текущей revision, иначе — 409 revision_conflict. DELETE /v1/workflows/blocks/{id} — мягкое удаление: блок перестаёт появляться в списке и разрешаться по id.
Запуск
POST /v1/workflows/runs компилирует и отправляет граф через тот же общий конвейер отправки, что и POST /v1/generations: корневые узлы уходят в работу сразу, остальные — по мере готовности своих входов, и опрашиваются через GET /v1/workflows/runs/{id}. Заголовок Idempotency-Key обязателен и играет ту же роль, что и на POST /v1/generations — durable-идентичность запуска: повтор с тем же ключом и тем же графом отвечает записью первой отправки, не запуская модерацию и списание второй раз. Подробнее о повторах — на странице идемпотентности.
curl -s -i -X POST https://api.coremind.ru/v1/workflows/runs \
-H "Authorization: Bearer cmk_..." \
-H "Idempotency-Key: 8f3c1d2e-5a47-4b90-9a1e-2f6b0c7d4e11" \
-H "Content-Type: application/json" \
-d '{"template_id": "<id>", "graph": {"nodes": [...], "edges": [...]}}'Ответ принятого запуска — 202, заголовок Location называет адрес, по которому появится статус. Ключ аккаунта без активного платного тарифа отвечает 402 payment_required — то же разделение, что применяет POST /v1/generations; сессия без тарифа запускать может.
GET /v1/workflows/runs перечисляет историю запусков аккаунта, новые первыми, в курсорном конверте: следующую страницу открывает ?cursor=, размер страницы фиксирован сервером. GET /v1/workflows/runs/{id} отдаёт запуск и каждый его узел в той же форме, в которой отвечают свежий опрос или сама отправка: 202, пока запуск ещё RUNNING, 200 — для уже завершённого. Чужой или неизвестный id отвечает 404 not_found.
curl -s https://api.coremind.ru/v1/workflows/runs/<id> \
-H "Authorization: Bearer cmk_..."Шаг цепочки — это обычная генерация, и его файлы отдаются так же: каждая ссылка на результат в ответе запуска ведёт на GET /v1/generations/{id}/outputs/{kind}/{index}, а скачивание требует области generations:read. Ключу, который и запускает цепочки, и забирает их результаты, нужны обе области.
POST /v1/workflows/runs/{id}/cancel останавливает каждый узел, который ещё не начался; уже отправленный узел доисполняется и попадает в историю как обычно. Кредиты этот маршрут не двигает — неначатый узел ещё не был списан, возвращать нечего, — поэтому платёжного гейта у него нет. Уже завершённый запуск отвечает 409 resource_conflict, чужой или неизвестный id — 404 not_found.
Оценка цены
POST /v1/workflows/estimate считает, во что обойдётся POST /v1/workflows/runs для этого же графа — сумму цены каждого узла со скидкой тарифа там, где её получает сам запуск. Бесплатная квота к узлам цепочки не применяется: даже модель с бесплатным лимитом в студии в цепочке оплачивается с баланса. Маршрут ничего не создаёт и не списывает, ему хватает области workflows:read.
curl -s -X POST https://api.coremind.ru/v1/workflows/estimate \
-H "Authorization: Bearer cmk_..." \
-H "Content-Type: application/json" \
-d '{"graph": {"nodes": [...], "edges": [...]}}'Повтор шага итератора
POST /v1/workflows/retry-item относится к одному узлу итератора внутри уже существующего запуска: тело называет workflow_id, node_key, template_id и action. action: "preview" отвечает ценой повтора, 200, ничего не списывая и не требуя Idempotency-Key для дедупликации. action: "run" отправляет повтор ровно как POST /v1/workflows/runs и отвечает 202. Заголовок Idempotency-Key обязателен на каждом вызове независимо от action, но дедуплицирует только "run". Каждый другой узел итератора, у которого тот же неудачный элемент, повторяется вместе с ним.
curl -s -X POST https://api.coremind.ru/v1/workflows/retry-item \
-H "Authorization: Bearer cmk_..." \
-H "Idempotency-Key: 8f3c1d2e-5a47-4b90-9a1e-2f6b0c7d4e11" \
-H "Content-Type: application/json" \
-d '{"workflow_id": "<run_id>", "node_key": "<node_id>", "template_id": "<id>", "action": "run"}'Коды ошибок
Сверх общего словаря кодов ошибок этот префикс использует:
revision_conflict—expected_revisionвPATCHразошёлся с текущей ревизией записи.resource_conflict— потолок цепочек или блоков аккаунта, запись уже в корзине, или отмена уже завершённого запуска.unreadable_record— история промптов ссылается на узел, формат которого не разобрать.moderation_blocked— промпт или входное изображение узла не прошли модерацию, как и на обычной отправке.payment_required,insufficient_credits— запуск или подтверждённый повтор ключом без активного тарифа или без достаточного баланса.origin_mismatch— изменяющий запрос по сессионной куке пришёл с недопустимогоOrigin; на этот отказ не распространяются заголовки лимита частоты — он происходит раньше лимитера.not_found— чужой, несуществующий или закрытый для роли аккаунтаid; владение отдельно не подтверждается и не опровергается.