442 lines
22 KiB
Markdown
442 lines
22 KiB
Markdown
# 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.
|
||
|
||
### OpenAPI preview upload
|
||
|
||
`POST /api/admin/workspaces/{workspace_id}/imports/openapi/preview` принимает только `multipart/form-data` с ровно одним полем `file`. Допускаются UTF-8 `.yaml`, `.yml` и `.json` размером 1..256 KiB с MIME, согласованным с расширением (для YAML/JSON также допустим `application/octet-stream`). JSON `{document}` не является production contract. Ответы об ошибках локализуются через `Accept-Language`, не раскрывают source/digest/path и могут содержать только canonical `X-Request-ID` и `X-Trace-ID` для восстановления.
|
||
|
||
`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` - внутренняя ошибка.
|