API¶
Справочник HTTP API Control Plane. Все endpoints живут под /api/v1, поля
тела и ответа в camelCase. Статья описывает общие правила (аутентификация,
заголовки, пагинация, идемпотентность, формат ошибок) и перечисляет каждый
endpoint с правом и назначением, сгруппировав их по ресурсам. Справочник для
разработчиков интеграций и харнессов.
Машинная схема
Полная схема OpenAPI отдаётся самим сервисом: GET /openapi.json,
Swagger UI — GET /docs. Справочник ниже составлен по той же схеме и по
коду команд, которые проверяют права.
Базовый адрес¶
| Где | Адрес |
|---|---|
| Внутри сети compose | http://control-plane-api:8000 |
| С хоста (по умолчанию только loopback) | http://127.0.0.1:${CP_HOST_PORT:-18000} |
| Снаружи через периметр | https://platform.example.com (маршруты /api/v1/*, /health/*, /metrics, /docs*, /openapi.json) |
Схема периметра описана в статье Периметр и TLS.
Аутентификация¶
- IAM access token: права берутся из привязки identity к principal и
сужаются scopes токена (
control-plane:read,control-plane:write,control-plane:admin). - Legacy-ключ
cp_<prefix>_<secret>принимается только приCP_LEGACY_API_KEYS_ENABLED=true. POST /api/v1/bootstrapпринимает толькоBearer <CP_BOOTSTRAP_TOKEN>./health/live,/health/readyи/metricsоткрыты без аутентификации.
Actor всегда вычисляется из credential. Подробности — в статье Авторизация и права.
Заголовки протокола¶
| Заголовок | Направление | Семантика |
|---|---|---|
Idempotency-Key |
запрос, мутации | идемпотентное выполнение, см. раздел Идемпотентность |
Idempotency-Replayed: true |
ответ | ответ взят из сохранённого, команда не выполнялась повторно |
If-Match: "<entity>-<version>" |
запрос | оптимистичная блокировка: PATCH задачи, workspace, типа workspace, проекта, роли, скилла, цели и комментария; :complete задачи; :transition проекта; config-revisions/{n}:activate |
ETag |
ответ | "<entity>-<version>" у GET задачи (task-), workspace (workspace-), типа workspace (workspace_type-), проекта (project-), роли (role-), скилла (skill-<rowVersion>), цели (goal-), комментария (comment-); у GET /tools — viewHash |
If-None-Match |
запрос | для GET /tools: 304, пока ревизии каталога и политики не изменились |
X-Request-ID |
оба | принимается или генерируется; возвращается в ответе и в error.requestId |
X-Correlation-ID |
запрос | попадает в correlationId событий |
X-Run-Id |
оба | сквозной trace-коррелятор (^[A-Za-z0-9._:-]{1,128}$, иначе генерируется); пишется в traceRunId событий, outbox, логи и заголовок к memory-service. Это не сущность Run |
Ошибки If-Match:
| Ситуация | Ответ |
|---|---|
| заголовка нет | 428 if_match_required |
| заголовок нельзя разобрать | 400 invalid_if_match |
| версия не совпала | 409 version_conflict, в details.currentVersion — текущая версия |
Пагинация¶
limit: по умолчанию 50, максимум 200. Значение вне диапазона даёт422 invalid_limit.- Курсор непрозрачен. Чужой курсор или курсор от другого порядка сортировки
даёт
422 invalid_cursor. - Списки сущностей сортируются стабильно по
(created_at, id), новые первыми. - Журналы прогона (
/runs/{id}/checkpoints,/runs/{id}/actions) идут поseq, старые первыми, курсор привязан к прогону. Безlimitиcursorжурнал отдаётся целиком одной страницей (nextCursor: null). - Комментарии задачи — единственная сущностная выборка от старых к новым. Её курсор имеет собственный формат.
GET /tasks?sort=startDate|dueDate: ближайшие первыми, задачи без даты — в конце. Неизвестныйsortдаёт422 invalid_sort.GET /work/availableможет вернуть страницу корочеlimitпри непустомnextCursor.
События¶
GET /events — отдельный контракт:
| Параметр | Смысл |
|---|---|
cursor |
непрозрачный курсор ec1_… |
after |
целочисленный sequence старого формата; адаптируется сервером |
tail=N |
последние N стабильных событий |
entityType, entityId |
фильтр по сущности |
types |
префиксы типа событий, до 20 |
workspaceId |
поддерево workspace; events.read проверяется на нём |
limit |
размер страницы |
Ответ: {items[], nextCursor, hasMore}, у каждого события есть своё поле
cursor. При пустой странице nextCursor повторяет переданный.
Малформированный курсор даёт 422 invalid_cursor, курсор будущей версии —
422 unsupported_cursor_version.
WebSocket: WS /api/v1/events/ws?after=<cursor>, право events.read. Коды
закрытия: 4401 — нет credentials, 4403 — нет права, 4404 — нет workspace
фильтра, 4400 — плохой курсор или фильтр, 4503 — PDP недоступен. Фильтры
и SDK потребителя — в статье Подписки на события.
Неизвестные query-параметры¶
Сервер не игнорирует query-параметры молча. Параметр, которого endpoint не
объявляет, даёт 400 invalid_request, и выборка не выполняется:
{
"error": {
"code": "invalid_request",
"message": "Request does not match the API contract",
"details": {"errors": [{"loc": "query.assignedToMee", "message": "Unknown query parameter: ..."}]},
"requestId": "req_..."
}
}
Правило действует на всех endpoints /api/v1, кроме WebSocket. Не добавляйте
служебные параметры вроде cache-buster _=: они тоже будут отклонены.
Неизвестные поля в JSON-теле также отклоняются (extra="forbid"), кроме тела
компиляции манифеста.
Идемпотентность¶
Любая мутация (POST-команда или PATCH) принимает заголовок Idempotency-Key
длиной 1–200 символов, иначе 422 invalid_idempotency_key.
| Ситуация | Результат |
|---|---|
| первый запрос | выполняется, ответ сохраняется на CP_IDEMPOTENCY_TTL_SECONDS (сутки) |
| повтор с тем же ключом, методом, путём, телом и principal | сохранённый ответ и Idempotency-Replayed: true |
| тот же ключ с другим телом или другим principal | 409 idempotency_key_reused |
| параллельный дубль, пока первый не завершился | ждёт до CP_IDEMPOTENCY_WAIT_TIMEOUT_SECONDS (10 с), затем 409 idempotency_in_flight |
| исполнитель упал на полпути | незавершённая запись живёт не дольше CP_IDEMPOTENCY_PENDING_TTL_SECONDS (60 с) |
Одноразовые секреты не сохраняются: повтор выпуска API-ключа вернёт
key: null. Для двух команд ключ обязателен: POST /runs/{id}/control-messages
и POST /runs/{id}/child-handles (без него 422 idempotency_key_required).
Для :handoff, harness-manifest:compile и :revoke дочернего handle ключ
настоятельно рекомендуется: повтор без него после неоднозначного ответа
становится новой командой.
Правило клиента
Один логический вызов — один ключ на все транспортные повторы. Повтор
HTTP-запроса не должен превращаться во вторую бизнес-команду. SDK
control_plane_client делает это сам.
Формат ошибок¶
Единый конверт с честными HTTP-кодами. Ответа 200 с ошибкой внутри не
бывает.
{
"error": {
"code": "task_already_claimed",
"message": "Task already has an active claim",
"details": {"taskId": "...", "claimId": "...", "expiresAt": "..."},
"requestId": "req_..."
}
}
Клиенту следует опираться на code и details, а не на текст message.
| HTTP | Типичные error.code |
|---|---|
| 400 | invalid_request (нарушение контракта, в т.ч. неизвестный параметр; details.errors[].loc), invalid_if_match, invalid_skill_inputs, idempotency_key_required |
| 401 | invalid_credentials |
| 403 | permission_denied (details.required), principal_not_active, delegation_required, claim_holder_mismatch, session_owner_mismatch, bootstrap_disabled, permission_escalation, not_eligible, run_holder_mismatch, tool_not_authorized, child_grant_exceeded, skill_permission_denied, skill_side_effect_not_authorized, run_owner_mismatch, run_id_required, scope_not_granted |
| 404 | not_found, tool_not_found (объект чужого tenant'а тоже даёт 404: существование не раскрывается) |
| 409 | version_conflict, stale_claim, task_already_claimed, task_claimed, session_expired, session_not_active, claim_expired, claim_not_active, claim_not_expired, idempotency_key_reused, idempotency_in_flight, already_bootstrapped, task_already_completed, task_not_ready, run_already_active, run_not_active, run_in_progress, approval_required, approval_already_decided, budget_exceeded, action_already_finished, skill_not_invocable, stale_invocation_lease, outcome_not_replayable, snapshot_stale, pack_version_conflict, retention_blocked_by_consumer, конфликты уникальности (*_exists, *_conflict) |
| 413 | request_too_large — тело больше CP_MAX_BODY_BYTES |
| 422 | доменная валидация: invalid_*, task_not_claimable, task_cancelled, empty_update, dependency_cycle, workspace_cycle, workspace_archived, unsupported_protocol_version, server_authoritative_section, secret_material_rejected, child_grant_exceeds_parent, child_result_too_large, cursor_must_not_advance, workspace_not_root, pack_*, snapshot_invalid и др. |
| 428 | if_match_required |
| 500 | internal_error — без стектрейса, подробности в логе по requestId |
| 502 | memory_unavailable — memory-service не обработал проксируемый запрос (details.memoryStatus, details.retryable) |
| 503 | policy_unavailable, memory_disabled; readiness — БД недоступна или миграции не применены |
Полный реестр кодов всех сервисов — в Коды ошибок.
Служебные endpoints¶
| Метод и путь | Аутентификация | Ответ |
|---|---|---|
GET /health/live |
нет | {"status": "alive"} |
GET /health/ready |
нет | 200 {"status": "ready", "revision": "<alembic>"}; 503 с reason: database_unreachable или migrations_pending (dbRevision, headRevision) |
GET /metrics |
нет | метрики в формате Prometheus: http_requests_total, active_harness_sessions, active_claims, active_runs, context_adapter_*, stale_fencing_rejections_total, authz_* и др. |
GET /openapi.json, GET /docs |
нет | схема OpenAPI и Swagger UI |
/metrics открыт
Эндпоинт не аутентифицирован. Закрывайте его на уровне периметра, см. Мониторинг и здоровье.
Справочник endpoints¶
Колонка «Право» перечисляет permission, которое проверяет сервер. «Владелец»
означает держателя сессии, claim или прогона. {ref} у задачи — это UUID или
publicId (TASK-000123).
Bootstrap¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /bootstrap |
Bearer <CP_BOOTSTRAP_TOKEN> |
один раз создать tenant, admin-principal, admin API-ключ и (с iamBinding) IAM-привязку администратора |
Тело: tenantSlug (^[a-z0-9][a-z0-9-]*$, 2–63), tenantName,
adminDisplayName, tenantId? (UUID tenant'а, общий с IAM),
iamBinding? {issuer, iamTenantId, iamPrincipalId}. Ответ 201:
{tenant, adminPrincipal, apiKey (с полным key), iamBinding}.
Principals, ключи, привязки, делегирование¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /principals |
principals.write |
создать principal (kind: human, agent, service; displayName, status, metadata) |
| GET | /principals |
principals.read |
список (?kind=) |
| GET | /principals/{id} |
principals.read |
principal |
| POST | /principals/{id}/api-keys |
principals.write |
выпустить legacy-ключ {permissions, expiresAt?}; полный ключ — только в этом ответе |
| POST | /api-keys/{id}:revoke |
principals.write |
отозвать ключ |
| GET | /principals/{id}/iam-bindings |
principals.read |
IAM-привязки principal, включая отозванные (без пагинации) |
| POST | /principals/{id}/iam-bindings |
principals.write |
upsert привязки {issuer, iamTenantId, iamPrincipalId, permissions}; 201 / 200 |
| POST | /iam-bindings/{id}:revoke |
principals.write |
закрыть вход identity |
| POST | /delegations |
delegations.manage |
делегирование человек → агент |
| GET | /delegations |
delegations.manage |
список |
| POST | /delegations/{id}:revoke |
delegations.manage |
отозвать |
Сессии и харнесс¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /sessions |
sessions.open |
открыть сессию; блок harness, onBehalfOf требует delegation |
| GET | /sessions |
sessions.manage |
список (?status=) |
| GET | /sessions/{id} |
владелец или sessions.manage |
сессия |
| POST | /sessions/{id}:heartbeat |
владелец или sessions.manage |
продлить аренду (ttlSeconds?) |
| POST | /sessions/{id}:close |
владелец или sessions.manage |
закрыть и снять claims сессии |
| GET | /harness/context |
аутентификация | self-контекст харнесса (?sessionId=) |
| GET | /work/available |
tasks.read |
доступная работа (workspaceId, includeDescendants, projectId, includeSubprojects, assigneeId, assignedToMe) |
| GET | /tools |
tasks.read |
поиск инструментов (query, runId); ETag, If-None-Match |
| GET | /tools/{ref} |
tasks.read |
инструмент по uuid, name или name@version (?runId=); вне политики — 404 tool_not_found |
Протокол подробно описан в статье Харнесс-протокол.
Типы задач¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /task-types |
task_types.manage |
следующая неизменяемая версия ключа (lifecycleSchema, fieldSchema, approvalSchema, execution) |
| GET | /task-types |
task_types.read |
список (?key=&status=) |
| GET | /task-types/{id} |
task_types.read |
версия типа |
| POST | /task-types/{id}:deprecate |
task_types.manage |
вывести версию из оборота (идемпотентно) |
См. Типы задач и статусы и Пакеты каталога.
Задачи¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /tasks |
tasks.write |
создать задачу |
| GET | /tasks |
tasks.read |
список: status, systemStatusCategory, typeKey, priority, ownerId, assigneeId, workspaceId, includeDescendants, projectId, includeSubprojects, startFrom, startTo, dueFrom, dueTo, sort (createdAt, startDate, dueDate), goalId |
| GET | /tasks/{ref} |
tasks.read |
задача (+ETag) |
| PATCH | /tasks/{ref} |
tasks.write |
изменить (If-Match; при живом claim — claimId и fencingToken) |
| GET | /tasks/{ref}/claimability |
tasks.read |
можно ли взять и почему нет |
| GET | /tasks/{ref}/transitions |
tasks.read |
куда можно перейти из текущего статуса (route: update или complete) |
| POST | /tasks/{ref}:claim |
tasks.claim |
атомарный claim {sessionId, ttlSeconds?, intent?} → fencing token |
| POST | /tasks/{ref}:complete |
tasks.write |
завершить (If-Match; при живом claim — claimId и fencingToken) |
| POST | /tasks/{ref}:start-run |
tasks.claim |
прогон под живым claim {claimId, fencingToken, input?, maxDurationSeconds?, maxActions?} |
| POST | /tasks/{ref}/relations |
tasks.write |
связь {toTask, type}: parent, blocks, depends_on, spawned_by, related_to; циклы — 422 |
| GET | /tasks/{ref}/relations |
tasks.read |
связи в обе стороны |
| DELETE | /tasks/{ref}/relations/{relationId} |
tasks.write |
удалить связь |
| GET | /tasks/{ref}/requirements |
tasks.read |
требования (roles, capabilities, skills) |
Пример создания задачи:
curl -s -X POST https://platform.example.com/api/v1/tasks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"title": "Добавить индекс на events(tenant_id, tx_id)",
"description": "Запросы журнала упираются в seq scan",
"priority": "high",
"typeKey": "coding-task",
"workspaceId": "<workspace-id>",
"requirements": {"skills": ["git.merge@1"]},
"acceptance": [{"key": "tests", "kind": "deterministic", "description": "make test проходит"}]
}'
Поля POST /tasks: title (1–500), description, priority (critical,
high, medium, low; по умолчанию medium), status (по умолчанию
initialStatus типа), typeId, typeKey, typeVersion (по умолчанию
системный тип task), ownerId, assigneeId, workspaceId, customFields
(проверяются по fieldSchema версии типа), startDate, dueDate,
parentTask (связь parent создаётся атомарно), requirements, goalId,
origin (неизменяем; без него ядро выводит parent, human или harness),
acceptance[], evidence[]. Семантика — в статьях Модель работы
и Цели, приёмка и evidence.
Цели¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /goals |
goals.write |
цель (title, desiredState, criteria[], ownerId, workspaceId, parentGoalId, createdFrom) |
| GET | /goals |
goals.read |
список (status, workspaceId, ownerId, parentGoalId) |
| GET | /goals/{id} |
goals.read |
цель (+ETag goal-<v>) |
| PATCH | /goals/{id} |
goals.write |
изменить (If-Match); status: active, achieved, abandoned; цикл — 422 goal_cycle |
| GET | /goals/{id}/work |
goals.read + tasks.read |
задачи цели, новые сверху (includeSubgoals, systemStatusCategory) |
Комментарии к задаче¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /tasks/{ref}/comments |
tasks.write |
добавить {body, runId?, artifactId?}; автор — вызывающий |
| GET | /tasks/{ref}/comments |
tasks.read |
тред, от старых к новым |
| GET | /tasks/{ref}/comments/{id} |
tasks.read |
комментарий (+ETag comment-<v>) |
| PATCH | /tasks/{ref}/comments/{id} |
tasks.write, только автор |
исправить (If-Match); прежний текст — ревизия |
| GET | /tasks/{ref}/comments/{id}/revisions |
tasks.read |
история правок (append-only) |
Claims¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| GET | /claims |
tasks.read |
список (taskId, sessionId, status) |
| GET | /claims/{id} |
tasks.read |
claim |
| POST | /claims/{id}:heartbeat |
держатель или claims.manage |
продлить аренду |
| POST | /claims/{id}:release |
держатель или claims.manage |
освободить; задача → releaseStatus типа, если ребро объявлено |
| POST | /claims/{id}:reclaim |
tasks.claim |
перехватить истёкший claim (новый token) |
Прогоны (runs)¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| GET | /runs |
tasks.read |
список (taskId, claimId, status) |
| GET | /runs/{id} |
tasks.read |
прогон |
| GET | /runs/{id}/context |
tasks.read |
Run Context |
| POST | /runs/{id}:succeed |
tasks.claim, владелец |
успех {output?, completeTask=true} |
| POST | /runs/{id}:fail |
владелец или claims.manage |
неудача (задачу не трогает) |
| POST | /runs/{id}:cancel |
владелец или claims.manage |
отмена |
| POST | /runs/{id}:suspend |
tasks.claim, владелец живого claim |
приостановить {reason, waitingForApprovalId?} |
| POST | /runs/{id}:handoff |
tasks.claim |
передать другому харнессу (рекомендуется Idempotency-Key) |
| POST | /runs/{id}:request-cancel |
tasks.write или claims.manage |
кооперативный сигнал отмены |
| POST | /runs/{id}/checkpoints |
tasks.claim, владелец живого claim |
checkpoint {kind, data} |
| GET | /runs/{id}/checkpoints |
tasks.read |
checkpoints по seq |
| POST | /runs/{id}/actions |
tasks.claim, владелец живого claim |
действие {action, status, skill?, externalReference?, metadata}; бюджет → 409 budget_exceeded |
| POST | /runs/{id}/actions/{actionId}:finish |
tasks.claim |
завершить started-действие |
| GET | /runs/{id}/actions |
tasks.read |
журнал действий по seq |
| GET | /runs/{id}/harness-manifest |
tasks.read |
манифест (?version=) |
| GET | /runs/{id}/harness-manifests |
tasks.read |
история версий |
| POST | /runs/{id}/harness-manifest:compile |
tasks.claim, владелец живого claim |
пересборка: 200 без изменений, 201 новая версия |
| POST | /runs/{id}/harness-manifest/ephemeral |
tasks.claim |
пометка {kind, summary, data} |
| POST | /runs/{id}/control-messages |
tasks.write; force_cancel — claims.manage |
control-сообщение (Idempotency-Key, expectedRunVersion обязательны) |
| GET | /runs/{id}/control-messages |
tasks.read |
сообщения, курсор rc1_… |
| POST | /runs/{id}/control-messages/{messageId}:acknowledge |
tasks.claim, держатель живого claim |
подтвердить applied, rejected или superseded |
| POST | /runs/{id}/child-handles |
tasks.claim + tasks.write, владелец живого claim |
запустить дочерний прогон; 201 новый, 200 повтор correlationId |
| GET | /runs/{id}/child-handles |
tasks.read |
handles прогона (?active=true, курсор cd1_…) |
| GET | /child-handles/{idOrToken} |
tasks.read |
статус и результат по id или ch1_… |
| POST | /child-handles/{id}:revoke |
держатель родительского прогона или claims.manage |
отозвать {reason, cancelChild} |
См. Исполнение — claims и runs и Харнесс-протокол.
Артефакты¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /artifacts |
artifacts.write |
append-only ссылка на результат: type, name, task?, runId?, workspaceId?, uri?, content?, metadata, supersedesArtifactId? |
| GET | /artifacts |
artifacts.read |
список (taskId, runId, workspaceId, type) |
| GET | /artifacts/{id} |
artifacts.read |
артефакт |
Approvals¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /approvals |
approvals.manage |
запрос; ровно одно из requiredRoleId / assignedPrincipalId; gate: true требует task и блокирует claim и complete |
| GET | /approvals |
approvals.read |
список (status, taskId) |
| GET | /approvals/{id} |
approvals.read |
approval |
| POST | /approvals/{id}:approve |
approvals.decide + eligibility |
одобрить |
| POST | /approvals/{id}:reject |
approvals.decide + eligibility |
отклонить |
| POST | /approvals/{id}:cancel |
approvals.manage; для gate — автор или eligible-решатель |
отменить |
| GET | /approvals/{id}/outcome |
approvals.read |
объявленный исход решения и статус каждого действия |
| POST | /approvals/{id}:replay-outcome |
approvals.decide (решивший или admin) |
продолжить упавший или зависший исход с первого невыполненного действия; иначе 409 outcome_not_replayable |
См. Approvals.
События и наблюдения¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| GET | /events |
events.read |
журнал (см. раздел Пагинация событий) |
| WS | /events/ws?after=<cursor> |
events.read |
поток событий |
| POST | /observations |
observations.write |
явная запись знания; повтор (source, dedupKey) → 200 deduplicated |
Контекст и знания¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /context |
аутентификация (task/runId — tasks.read, projectId — projects.read, память — events.read) |
operational-контекст и пакет памяти |
| POST | /knowledge/snapshots |
observations.write на workspace:<workspaceId> |
снимок источника → memory-service (тело до 8 МиБ) |
| POST | /knowledge/packs |
principal из CP_KNOWLEDGE_PACK_ADMINS |
регистрация пакета знаний |
| PUT | /workspaces/{id}/knowledge-packs |
workspaces.manage на workspace:<id> |
пакеты и strict namespace дерева (только корень) |
Workspaces и типы workspace¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /workspace-types |
workspaces.manage |
тип workspace |
| GET | /workspace-types |
workspaces.read |
список (status) |
| GET | /workspace-types/{id} |
workspaces.read |
тип (+ETag) |
| PATCH | /workspace-types/{id} |
workspaces.manage |
изменить (If-Match) |
| POST | /workspace-types/{id}:archive |
workspaces.manage |
архивировать; используется — 422 workspace_type_in_use |
| POST | /workspaces |
workspaces.manage |
workspace (typeId или typeKey, customFields) |
| GET | /workspaces |
workspaces.read |
список (parentId, rootsOnly, status) |
| GET | /workspaces/tree |
workspaces.read |
дерево (rootId, depth, includeArchived, includeProjects) → {roots: [...]} |
| GET | /workspaces/{id} |
workspaces.read |
workspace (+ETag) |
| PATCH | /workspaces/{id} |
workspaces.manage |
изменить (If-Match) |
| POST | /workspaces/{id}:archive |
workspaces.manage |
архивировать (без активных детей) |
| POST | /workspaces/{id}:move |
workspaces.manage |
перенести {newParentId} (null — в корень); цикл или ослабление governance — 422 |
| POST | /workspaces/{id}/members |
workspaces.manage |
добавить участника |
| GET | /workspaces/{id}/members |
workspaces.read |
участники |
| POST | /workspaces/{id}/members/{principalId}:remove |
workspaces.manage |
удалить участника |
Проекты и шаблоны¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /project-templates |
project_templates.manage |
следующая неизменяемая версия шаблона |
| GET | /project-templates |
project_templates.read |
список (key, status) |
| GET | /project-templates/{id} |
project_templates.read |
шаблон |
| POST | /project-templates/{id}:deprecate |
project_templates.manage |
вывести из оборота |
| POST | /projects |
projects.manage |
проект: workspaceId или workspaceSlug (workspace и профиль создаются атомарно); второй профиль — 409 project_exists |
| GET | /projects |
projects.read |
список (workspaceId, status, statusKey, systemStatusCategory, templateKey, externalSystem, externalType, externalId) |
| GET | /projects/{id} |
projects.read |
проект (+ETag) |
| PATCH | /projects/{id} |
projects.manage |
изменить (If-Match) |
| POST | /projects/{id}:archive |
projects.manage |
архивировать (идемпотентно) |
| POST | /projects/{id}:transition |
projects.manage |
переход статуса (If-Match; только объявленные) |
| GET | /projects/{id}/effective-config |
projects.read |
конфигурация и provenance по слоям |
| GET | /projects/{id}/config-revisions |
projects.read |
ревизии конфигурации |
| POST | /projects/{id}/config-revisions |
projects.manage |
новая ревизия (не активирует) |
| POST | /projects/{id}/config-revisions/{revision}:activate |
projects.manage |
активировать ревизию (If-Match) |
| GET | /projects/{id}/external-references |
projects.read |
внешние ссылки проекта |
| POST | /projects/{id}/external-references |
projects.manage |
добавить (201) или обновить metadata (200); чужая сущность — 409 |
Внешние ссылки¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /external-references |
по entityType: project → projects.manage, task → tasks.write |
зарегистрировать ссылку; 201 новая, 200 тот же ключ, 409 external_reference_conflict |
| GET | /external-references |
право чтения типа | прямой поиск ?entityType=&entityId= или обратный ?externalSystem=&externalType=&externalId= |
Организационная модель¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /roles |
org.manage |
роль (slug уникален в scope; workspaceId?) |
| GET | /roles |
org.read |
список (workspaceId) |
| GET | /roles/{id} |
org.read |
роль (+ETag) |
| PATCH | /roles/{id} |
org.manage |
изменить (If-Match) |
| POST | /capabilities |
org.manage |
capability |
| GET | /capabilities |
org.read |
список |
| GET | /capabilities/{id} |
org.read |
capability |
| POST | /principals/{id}/roles |
org.manage |
назначить {roleId, workspaceId?} |
| GET | /principals/{id}/roles |
org.read или principals.read |
роли principal |
| POST | /principals/{id}/roles/{roleId}:revoke |
org.manage |
снять |
| POST | /principals/{id}/capabilities |
org.manage |
назначить |
| GET | /principals/{id}/capabilities |
org.read или principals.read |
capabilities principal |
| POST | /principals/{id}/capabilities/{capabilityId}:revoke |
org.manage |
снять |
| POST | /principals/{id}/skills |
org.manage |
назначить скилл |
| GET | /principals/{id}/skills |
org.read или principals.read |
скиллы principal |
| POST | /principals/{id}/skills/{skillId}:revoke |
org.manage |
снять |
Скиллы и вызовы¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| POST | /skills |
org.manage |
опубликовать версию (name + version уникальны; contract, sideEffects, riskLevel) |
| GET | /skills |
org.read |
список (name, status) |
| GET | /skills/{ref} |
org.read, skills.invoke или skills.execute |
версия по id, name@version или name (+ETag skill-<rowVersion>) |
| PATCH | /skills/{id} |
org.manage |
If-Match; меняются только description и status вперёд (active → deprecated → disabled). config, inputSchema, outputSchema допустимы, только если совпадают с сохранёнными, иначе 409 skill_version_immutable; обратный переход статуса — invalid_status_transition |
| POST | /skills/{ref}:invoke |
skills.invoke |
вызов {inputs, idempotencyKey?, taskId?, runId?, approvalId?} → 201 новый, 200 повтор ключа |
| GET | /skill-invocations/{id} |
skills.invoke или skills.execute |
вызов (видит authority и исполнитель) |
| POST | /skill-invocations:claim |
skills.execute |
взять вызов {protocols, localEntrypoints, httpOrigins, mcpEndpoints, audiences, sessionId?, leaseSeconds?, invocationId?} → 200 {invocation, skill} или 204 |
| POST | /skill-invocations/{id}:heartbeat |
skills.execute |
продлить lease {fencingToken, leaseSeconds?} |
| POST | /skill-invocations/{id}:complete |
skills.execute |
результат {fencingToken, output, cost?}; output перепроверяется по схеме |
| POST | /skill-invocations/{id}:fail |
skills.execute |
неудача {fencingToken, error: {code, message?, retryable?, details?}} |
| POST | /skill-invocations/{id}:cancel |
authority вызова или org.manage |
отменить {reason?} |
Порядок проверок :invoke:
- право
skills.invoke, версия существует; - повтор
idempotencyKeyс теми жеinputsот того же principal возвращает существующий вызов, иначе409 idempotency_key_reuse; - версия вызываема (
409 skill_not_invocable,details.reason:disabled,no_contract,protocol_not_invocable); - при
idempotency: requiredключ обязателен (400 idempotency_key_required); inputsпроверяются по схеме (400 invalid_skill_inputs,details.errors[].path);runIdдолжен принадлежать вызывающему и быть в статусеrunning;requiredPermissionsконтракта проверяются на workspace задачи (403 skill_permission_denied), эффективная политика инструментов прогона —403 tool_not_authorizedили403 child_grant_exceeded;external_writeтребует одобренного gate-approval на той же нетерминальной задаче, который ещё не использовался для этой версии (409 approval_already_used), или основанияexecution(тип задачи закрепил эту версию, прогон в статусеrunning).
Успешный :complete при taskId создаёт артефакт skill_result. Подробнее
о контракте скилла — в skill-sdk.
Операции¶
| Метод | Путь | Право | Назначение |
|---|---|---|---|
| GET | /operations/context-adapter |
operations.read |
состояние доставки в память своего tenant'а |
| POST | /operations/context-adapter/{tenantId}:redrive |
operations.manage |
снять парковку и повторить ту же позицию {reason}; чужой tenant — 404 |
| POST | /operations/context-adapter/{tenantId}:rebuild |
operations.manage |
отмотать курсор {cursor?, reason}; только назад, иначе 422 cursor_must_not_advance |
| POST | /operations/journal:archive |
operations.manage |
перенести подтверждённую историю в архив {beforeSeconds?, maxEvents?} |
| POST | /operations/journal:prune |
operations.manage |
физически удалить архив {beforeSeconds?, maxEvents?} — данные теряются |
curl -s -X POST https://platform.example.com/api/v1/operations/journal:archive \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"beforeSeconds": 2592000, "maxEvents": 50000}'
Горизонт архивации ограничен минимумом consumer-курсоров и самым старым
недоставленным outbox-событием. Если consumer-курсоров нет совсем, ответ —
409 retention_blocked_by_consumer. Минимальный возраст события задаёт
CP_JOURNAL_RETENTION_MIN_AGE_SECONDS (30 суток). Процедуры описаны в
Резервное копирование и
Мониторинг и здоровье.
Пагинация событий¶
Курсор журнала непрозрачен (ec1_…) и выдаётся в порядке (tx_id, sequence)
под стабильным горизонтом. Подписка с выданного курсора получит всё, что
закоммитится позже: незавершённые транзакции сортируются строго после любой
выданной позиции. Каждое событие несёт sequence, type, entityType,
entityId, actorId, sessionId, correlationId, requestId,
traceRunId, payload, occurredAt и cursor. Каталог типов событий — в
статье События.
SDK¶
Официальный клиент — пакет control-plane-client (модуль
control_plane_client, класс ControlPlaneClient). Он сам выставляет
Idempotency-Key на логический вызов, обновляет IAM-токен и разбирает
конверт ошибок в исключения. См. Клиенты сервисов.