# 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=' ``` Для небезопасных 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=' ``` 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=' ``` ## 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=' \ -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//test-runs \ -b 'crank_session=' \ -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/. curl https://crank.example.com/api/admin/workspaces/ws_default/operations//publish \ -b 'crank_session=' \ -H 'If-Match: ""' \ -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=' \ -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=' \ -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//platform-api-keys \ -b 'crank_session=' \ -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=' ``` 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` - внутренняя ошибка.