Files
crank/docs/admin-api.md
T

438 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Admin API
Admin API используется веб-интерфейсом Crank. Его можно использовать и напрямую для автоматизации настройки.
Base path:
```text
/api/admin
```
Auth path:
```text
/api/auth
```
## Авторизация
Администратор входит по email и паролю. После входа сервер устанавливает
HttpOnly session cookie и возвращает `csrf_token` для browser mutations.
Первый production-admin создаётся через локальный одноразовый bootstrap token:
```bash
crank-migrate admin-auth bootstrap-create --email owner@example.com
```
После этого оператор открывает `/login`, вводит token и задаёт первый пароль.
Token одноразовый; replay возвращает generic unauthorized без раскрытия причины.
```bash
curl -i https://crank.example.com/api/auth/login \
-H 'Content-Type: application/json' \
--data '{
"email": "owner@example.com",
"password": "change-me"
}'
```
Дальше используйте cookie из ответа:
```bash
curl https://crank.example.com/api/auth/session \
-b 'crank_session=<cookie_value>'
```
Для небезопасных browser/API mutations с session cookie передавайте текущий
`csrf_token` в заголовке `x-csrf-token`. Cross-origin `/api/*` requests
отклоняются по умолчанию.
Endpoints:
- `GET /api/auth/bootstrap/status`
- `POST /api/auth/bootstrap/complete`
- `POST /api/auth/login`
- `POST /api/auth/logout`
- `GET /api/auth/session`
- `GET /api/auth/profile`
- `PATCH /api/auth/profile`
- `POST /api/auth/password`
## Capabilities
```bash
curl https://crank.example.com/api/admin/capabilities \
-b 'crank_session=<cookie_value>'
```
Community capabilities:
- protocol: `rest`;
- operation security level: `standard`;
- machine access: static agent API keys.
## Workspaces
Community работает с одним workspace.
- `GET /api/admin/workspaces`
- `GET /api/admin/workspaces/{workspace_id}`
- `PATCH /api/admin/workspaces/{workspace_id}`
Пример:
```bash
curl https://crank.example.com/api/admin/workspaces \
-b 'crank_session=<cookie_value>'
```
## Upstreams
Upstream хранит базовый URL внешнего REST API и необязательные статические заголовки.
- `GET /api/admin/workspaces/{workspace_id}/upstreams`
- `POST /api/admin/workspaces/{workspace_id}/upstreams`
- `PATCH /api/admin/workspaces/{workspace_id}/upstreams/{upstream_id}`
Пример создания:
```bash
curl https://crank.example.com/api/admin/workspaces/ws_default/upstreams \
-b 'crank_session=<cookie_value>' \
-H 'Content-Type: application/json' \
--data '{
"name": "Frankfurter",
"base_url": "https://api.frankfurter.dev",
"static_headers": {},
"auth_profile_id": null
}'
```
## Operations
Операция описывает один REST endpoint как MCP-инструмент.
Published Version неизменяема. Изменение после публикации создаёт следующую Draft revision; существующий Agent продолжает использовать exact bound version до явного rebinding и новой публикации Agent. Архивация запрещает новые изменения/привязки, но не удаляет опубликованные snapshots или историю.
- `GET /api/admin/workspaces/{workspace_id}/operations`
- `POST /api/admin/workspaces/{workspace_id}/operations`
- `POST /api/admin/workspaces/{workspace_id}/operations/analyze-quality`
- `GET /api/admin/workspaces/{workspace_id}/operations/{operation_id}`
- `PATCH /api/admin/workspaces/{workspace_id}/operations/{operation_id}`
- `DELETE /api/admin/workspaces/{workspace_id}/operations/{operation_id}`
- `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/versions`
- `GET /api/admin/workspaces/{workspace_id}/operations/{operation_id}/versions/{version}`
- `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/publish`
- `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/archive`
- `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/test-runs`
- `GET /api/admin/workspaces/{workspace_id}/operations/{operation_id}/export`
- `POST /api/admin/workspaces/{workspace_id}/operations/import`
Пример тестового запуска:
```bash
curl https://crank.example.com/api/admin/workspaces/ws_default/operations/<operation_id>/test-runs \
-b 'crank_session=<cookie_value>' \
-H 'Content-Type: application/json' \
--data '{
"version": 1,
"input": {
"base": "USD",
"quote": "EUR"
}
}'
```
Успешные поля Test Run сохранены. Каждый элемент `errors` дополнительно содержит
stable `code`, `stage`, `retryability`, `outcome_certainty` и безопасные
Request/Trace IDs ответа. При `outcome_unknown` автоматический повтор запрещён;
пользователь должен сверить результат во внешней системе.
Пример публикации:
```bash
# Сначала прочитайте актуальный strong ETag из GET /operations/<operation_id>.
curl https://crank.example.com/api/admin/workspaces/ws_default/operations/<operation_id>/publish \
-b 'crank_session=<cookie_value>' \
-H 'If-Match: "<opaque-operation-etag>"' \
-H 'Content-Type: application/json' \
--data '{ "version": 1 }'
```
Mutation contract revision 2 требует `If-Match` для `PATCH`, create-version, publish, archive, delete и изменяющего существующую Operation YAML upsert. Отсутствующий token возвращает `428 operation_precondition_required`, устаревший или относящийся к другому объекту — `409 operation_stale_version`. Token повторно проверяется под тем же PostgreSQL row lock, что и mutation. Summary возвращает `can_delete`, а detail — aggregate `availability` отдельно от exact Draft/Published version state. Exact version reads имеют отдельный content-stable ETag.
Portable export использует закрытый `format_version: "2"` contract из [`schemas/operation-export-v2.schema.json`](schemas/operation-export-v2.schema.json). Он исключает persistence IDs, lifecycle metadata, samples, wizard state и credentials. Legacy v1 принимается только при импорте и нормализуется в v2; exporter v1 не выдаёт. YAML import ограничен 256 KiB и возвращает только bounded codes `operation_yaml_too_large|operation_yaml_invalid|operation_yaml_unsupported` без raw parser text.
`analyze-quality` принимает payload операции и возвращает рекомендации:
```json
{
"blocking": false,
"findings": [
{
"severity": "warning",
"code": "tool_description_too_short",
"message": "Описание инструмента слишком короткое.",
"suggested_action": "Добавьте назначение, условия применения и ожидаемый успешный результат.",
"field_path": "tool_description.description"
}
]
}
```
## Samples
- `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/input-json`
- `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/output-json`
- `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/drafts/generate`
Samples используются для генерации схемы, стартового маппинга и сохранения тестовых примеров wizard-а.
## Secrets
- `GET /api/admin/workspaces/{workspace_id}/secrets`
- `POST /api/admin/workspaces/{workspace_id}/secrets`
- `GET /api/admin/workspaces/{workspace_id}/secrets/{secret_id}`
- `POST /api/admin/workspaces/{workspace_id}/secrets/{secret_id}/rotate`
- `DELETE /api/admin/workspaces/{workspace_id}/secrets/{secret_id}`
Пример создания token secret:
```bash
curl https://crank.example.com/api/admin/workspaces/ws_default/secrets \
-b 'crank_session=<cookie_value>' \
-H 'Content-Type: application/json' \
--data '{
"name": "production-api-token",
"kind": "token",
"value": "secret-token-value"
}'
```
После создания или ротации API возвращает только metadata. Значение секрета нельзя прочитать повторно.
Если secret используется Auth Profile, удаление отклоняется `409 secret_referenced_by_auth_profile`.
Create, rotate, delete и denied-delete пишут bounded audit event с actor,
credential ref, request id и trace id; plaintext, ciphertext и hash в event не
попадают.
## Auth profiles
- `GET /api/admin/workspaces/{workspace_id}/auth-profiles`
- `POST /api/admin/workspaces/{workspace_id}/auth-profiles`
- `GET /api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}`
Auth profile хранит ссылки на secrets и способ применения секрета к REST-запросу.
При выполнении Operation текущая версия Secret читается перед dispatch; rotation
Secret меняет credential для следующего execution без переписывания Operation
или Auth Profile refs.
## Agents
- `GET /api/admin/workspaces/{workspace_id}/agents`
- `POST /api/admin/workspaces/{workspace_id}/agents`
- `GET /api/admin/workspaces/{workspace_id}/agents/{agent_id}`
- `PATCH /api/admin/workspaces/{workspace_id}/agents/{agent_id}`
- `DELETE /api/admin/workspaces/{workspace_id}/agents/{agent_id}`
- `POST /api/admin/workspaces/{workspace_id}/agents/{agent_id}/versions`
- `GET /api/admin/workspaces/{workspace_id}/agents/{agent_id}/versions/{version}`
- `POST /api/admin/workspaces/{workspace_id}/agents/{agent_id}/publish`
- `POST /api/admin/workspaces/{workspace_id}/agents/{agent_id}/unpublish`
- `POST /api/admin/workspaces/{workspace_id}/agents/{agent_id}/archive`
- `POST /api/admin/workspaces/{workspace_id}/agents/{agent_id}/bindings`
- `POST /api/admin/workspaces/{workspace_id}/agents/tool-search/preview`
- `DELETE /api/admin/workspaces/{workspace_id}/agents/{agent_id}/bindings/{operation_id}`
Пример создания агента:
```bash
curl https://crank.example.com/api/admin/workspaces/ws_default/agents \
-b 'crank_session=<cookie_value>' \
-H 'Content-Type: application/json' \
--data '{
"slug": "currency-rates",
"display_name": "Курсы валют",
"description": "Агент с инструментами для получения курсов валют.",
"instructions": {},
"tool_selection_policy": {}
}'
```
Привязки и политика доступа сохраняются одной атомарной операцией:
```json
{
"bindings": [
{
"operation_id": "op_01",
"operation_version": 1,
"tool_name": "create_invoice",
"tool_title": "Создать счёт",
"enabled": true
}
],
"tool_selection_policy": {
"mode": "search",
"groups": [
{
"id": "finance",
"name": "Расчёты",
"description": "Счета, платежи и возвраты",
"tool_names": ["create_invoice"]
}
],
"search": { "max_results": 8 }
}
}
```
Старый формат тела из одного массива привязок поддерживается и сохраняет текущую политику агента. Предварительная проверка принимает те же `bindings` и `tool_selection_policy`, а также `query` и необязательный `group_ids`.
Agent catalog lifecycle (`agent-catalog-lifecycle-v9`) защищает опубликованный каталог от in-place mutation:
- `GET /agents/{agent_id}` возвращает strong `ETag`, связанный с workspace, Agent identity, current Draft version, latest Published version, availability и `catalog_revision`;
- mutation опубликованного Agent (`PATCH`, `DELETE`, `bindings`, `publish`, `unpublish`, `archive`) требует актуальный `If-Match`; отсутствующий precondition возвращает `428 agent_precondition_required`, устаревший — `409 agent_stale_revision`;
- Published Agent Version и его bindings immutable на уровне registry/DB; изменение каталога создаёт новый Draft/current version и отдельный publish;
- binding принимает только exact Published Operation Version из того же workspace; draft, archived, missing или foreign Operation Version отклоняются до publication;
- `catalog_revision` монотонно растёт при publish/unpublish/archive и используется MCP search/call для защиты от stale search results.
## Agent API keys
- `GET /api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys`
- `POST /api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys`
- `POST /api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys/{key_id}/revoke`
- `DELETE /api/admin/workspaces/{workspace_id}/agents/{agent_id}/platform-api-keys/{key_id}`
Пример создания ключа:
```bash
curl https://crank.example.com/api/admin/workspaces/ws_default/agents/<agent_id>/platform-api-keys \
-b 'crank_session=<cookie_value>' \
-H 'Content-Type: application/json' \
--data '{
"name": "Demo MCP client",
"scopes": ["read", "write"]
}'
```
Полное значение ключа доступно только в create response.
Дальше API возвращает только metadata: `id`, `name`, bounded prefix, `key_kind`,
`scopes`, `status` и timestamps. Hash и raw key никогда не возвращаются.
`key_kind = mcp_client` используется только для MCP client доступа.
`key_kind = approval` используется только для approval side-channel. Для approval
keys можно задать `allowed_origins`; значения должны быть точными
`http://`/`https://` origins без path/query/userinfo. MCP approval запрос с
чужим `Origin` отклоняется до исполнения side effect.
`revoke` немедленно прекращает доступ, включая уже существующие MCP session.
`DELETE` переводит уже revoked key в terminal metadata state `deleted`, но не
стирает provenance. Credential mutations пишут bounded audit event с actor,
credential ref, request id и trace id; raw key/hash в audit event не попадают.
Create response для `mcp_client` key дополнительно содержит ephemeral
`connection`: canonical MCP endpoint и copy-safe конфигурации для
поддерживаемых representative clients. Это единственная граница, где API
возвращает raw secret. При повторном `GET`, после обновления страницы, revoke
или delete возвращаются только metadata; потерянное значение не восстанавливается.
После неоднозначной ошибки create UI не должен автоматически повторять запрос:
оператор сначала обновляет metadata, затем осознанно создаёт или ротирует key.
## Getting Started onboarding
- `GET /api/admin/workspaces/{workspace_id}/onboarding`
- `POST /api/admin/workspaces/{workspace_id}/onboarding/events`
`GET` доступен только аутентифицированному участнику workspace и строит
ограниченную server-authoritative проекцию: `operation`, `test`,
`publish_operation`, `agent`, `key`, `mcp_connection`, `first_call`. Browser
не передаёт completed state и не может завершить domain step. Первый read
идемпотентно фиксирует server-owned eligible cohort, а terminal projection после
реального успеха идемпотентно фиксирует completion. Оба времени выдаются в UTC
RFC3339.
Ответ имеет `schema_version: 1`, opaque `revision`, `status`
(`in_progress|complete`), `eligible_since`, упорядоченные `steps`, точные
Operation/Agent/key references, canonical `mcp_endpoint` и, только после
успешного public `tools/call`, `first_call`. Каждый step содержит stable
`id`, `completed`, `status` (`pending|current|complete|regressed`),
`action_code` и `reason_code`. В `first_call` есть только безопасные
`log_id`, Agent/key/Operation/version, tool, timestamp, Request ID и Trace ID;
входной payload, raw key и upstream body в snapshot не попадают.
`POST /onboarding/events` принимает только presentation events
`started|resumed|dismissed|abandoned`, bounded `idempotency_key` и текущий
opaque `expected_revision`. Unknown fields и попытки отправить `eligible`,
`completed`, domain steps или client supplied cohort timestamp отклоняются.
Конфликт revision возвращает `409 onboarding_stale_revision` с recovery
`reload`; запрещённый event — `422 onboarding_event_not_allowed`. Повтор того
же idempotency key безопасен и не меняет server-derived progress.
## Logs и usage
- `GET /api/admin/workspaces/{workspace_id}/logs`
- `GET /api/admin/workspaces/{workspace_id}/logs/{log_id}`
- `GET /api/admin/workspaces/{workspace_id}/logs/export.csv`
- `GET /api/admin/workspaces/{workspace_id}/usage`
- `GET /api/admin/workspaces/{workspace_id}/usage/export.csv`
- `GET /api/admin/workspaces/{workspace_id}/usage/operations/{operation_id}`
- `GET /api/admin/workspaces/{workspace_id}/usage/agents/{agent_id}`
- `GET /api/admin/workspaces/{workspace_id}/approvals`
- `GET /api/admin/workspaces/{workspace_id}/approvals/{approval_id}`
Пример:
```bash
curl 'https://crank.example.com/api/admin/workspaces/ws_default/logs?limit=20' \
-b 'crank_session=<cookie_value>'
```
Logs list принимает bounded filters `period`, `created_after`,
`created_before`, `level`, `status`, `outcome_group`, `source`, `operation_id`,
`agent_id`, `search`, `limit` и opaque `cursor`. Explicit
`created_after/created_before` задают UTC RFC3339 half-open window `[start,
end)` и должны передаваться парой. Ответ имеет форму `{ "items": [...],
"next_cursor": "..." | null }`; cursor привязан к детерминированному порядку
`created_at desc, id desc` и не раскрывает host path или секреты. Log detail
возвращает безопасные preview-поля, `request_id`, `trace_id`, execution
taxonomy, точную Operation Version и связанную Operation/Agent metadata только
в рамках текущего workspace.
`logs/export.csv` применяет те же auth, scope и filters, что и list endpoint.
CSV ограничен по строкам/размеру, использует уже отредактированные previews и
экранирует spreadsheet-formula значения (`=`, `+`, `-`, `@`, tab, CR/LF в
начале cell). CSV остаётся локальным Admin response; Crank не отправляет usage
наружу.
Usage endpoints используют UTC half-open interval `[start, end)`. Они
принимают либо bounded `period`, либо explicit RFC3339
`created_after/created_before` пару. Overview возвращает workspace summary,
timeline, breakdown по Operation/Agent и outcome группы: `success`, `upstream`,
`client`, `schema`, `crank`. Эти группы позволяют отличать ошибки внешнего
upstream или пользовательского input от ошибок самого Crank; `request_id` и
`trace_id` не используются как labels или aggregation keys. `usage/export.csv`
создаётся сервером из того же scoped usage dataset, имеет bounded размер,
экранирует spreadsheet-formula значения и не зависит от текущего client-side
snapshot браузера.
Admin API approvals являются read-only operational view. Approve/deny выполняет
MCP approval side-channel с отдельным `approval` key, потому что этот ключ можно
ограничить конкретным Agent и `allowed_origins`. Admin list/get показывает
только bounded safe request summary и terminal response payload. Raw request
payload, approval key, auth headers, confirmation/control tokens и secret-like
значения не возвращаются.
## Ошибки
Admin API возвращает JSON-ошибки с человекочитаемым сообщением и контекстом, если он доступен.
Частые HTTP-коды:
- `400` - неверный payload;
- `401` - нет сессии;
- `403` - действие запрещено;
- `404` - сущность не найдена;
- `409` - конфликт состояния;
- `429` - rate limit;
- `500` - внутренняя ошибка.