Files
crank/docs/admin-api.md
T

22 KiB
Raw Blame History

Admin API

Admin API используется веб-интерфейсом Crank. Его можно использовать и напрямую для автоматизации настройки.

Base path:

/api/admin

Auth path:

/api/auth

Авторизация

Администратор входит по email и паролю. После входа сервер устанавливает HttpOnly session cookie и возвращает csrf_token для browser mutations.

Первый production-admin создаётся через локальный одноразовый bootstrap token:

crank-migrate admin-auth bootstrap-create --email owner@example.com

После этого оператор открывает /login, вводит token и задаёт первый пароль. Token одноразовый; replay возвращает generic unauthorized без раскрытия причины.

curl -i https://crank.example.com/api/auth/login \
  -H 'Content-Type: application/json' \
  --data '{
    "email": "owner@example.com",
    "password": "change-me"
  }'

Дальше используйте cookie из ответа:

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

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}

Пример:

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}

Пример создания:

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

Пример тестового запуска:

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 автоматический повтор запрещён; пользователь должен сверить результат во внешней системе.

Пример публикации:

# Сначала прочитайте актуальный 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. Он исключает 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 операции и возвращает рекомендации:

{
  "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:

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}

Пример создания агента:

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": {}
  }'

Привязки и политика доступа сохраняются одной атомарной операцией:

{
  "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}

Пример создания ключа:

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}

Пример:

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 - внутренняя ошибка.