External API v1: WR/ГЦК кабинеты

External API v1 для WR/ГЦК-кабинетов

External API даёт backend-сервисам доступ к WR/ГЦК-кабинетам пользователя Alitos. Через API можно читать данные, менять настройки и запускать долгие операции.

API предназначен только для server-to-server интеграций. Он не предоставляет raw WR proxy, cookies, пароли или upstream-токены.

API включается поэтапно. Если credential endpoints, /capabilities или OpenAPI возвращают 404, соответствующий маршрут ещё не опубликован для этой rollout-волны.

Адреса

Назначение

URL

Credentials и token exchange

https://api.alitos.io

Cabinet API

https://api.alitos.io/v1/external

OpenAPI

https://api.alitos.io/v1/external/openapi.yaml

OpenAPI определяет схемы запросов и ответов, обязательные поля, ограничения и коды ответа.

Быстрый старт

1. Получите credential

Пользователю нужно право external_api_access. Credential создаётся в настройках External API в Alitos и показывается только один раз.

Сохраните значение alitos_cred_... в secret manager. Resource endpoints не принимают credential напрямую.

Credential по умолчанию не истекает. Если политика компании требует ограниченный срок, при создании укажите expiresInDays от 1 до 365.

Управление credentials также доступно авторизованному Alitos-клиенту:

  • GET /external-access/credentials;

  • POST /external-access/credentials;

  • POST /external-access/credentials/{credentialId}/revoke.

2. Обменяйте credential на JWT

curl --request POST 'https://api.alitos.io/oauth/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode \
    'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
  --data-urlencode \
    'subject_token_type=urn:alitos:params:oauth:token-type:external-access-credential' \
  --data-urlencode \
    "subject_token=${ALITOS_EXTERNAL_CREDENTIAL}" \
  --data-urlencode \
    'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
  --data-urlencode \
    'resource=https://api.alitos.io/v1/external' \
  --data-urlencode \
    'scope=cabinet:read cabinet:settings:write'

JWT действует 60 минут. Refresh token не выдаётся. Если scope не передан, JWT получает все scopes credential.

Возьмите access_token из ответа и передавайте его как Bearer JWT.

Запрошенные scopes должны быть подмножеством scopes credential. Значение resource должно в точности совпадать с REST resource.

Кэшируйте JWT в памяти процесса. Интеграция должна автоматически выполнять новый exchange перед истечением срока.

3. Проверьте доступ и найдите кабинет

curl 'https://api.alitos.io/v1/external/capabilities' \
  --header "Authorization: Bearer ${ALITOS_EXTERNAL_JWT}"

curl 'https://api.alitos.io/v1/external/cabinets?q=example&limit=20' \
  --header "Authorization: Bearer ${ALITOS_EXTERNAL_JWT}"

curl 'https://api.alitos.io/v1/external/cabinets/123456789/context' \
  --header "Authorization: Bearer ${ALITOS_EXTERNAL_JWT}"

/capabilities возвращает effective scopes, allowlist, доступные группы операций и текущие лимиты.

Scopes и доступ

Scope

Операции

cabinet:read

Кабинеты, пространства, контекст, настройки, проекты и аналитика

cabinet:projects:write

Создание проектов, источники и состояния

cabinet:settings:write

Безопасные settings и analytics-config updates

cabinet:deals:read

Сделки и чувствительные контактные данные

cabinet:exports:run

Экспорт сделок и source analytics jobs

Scope ограничивает credential, но не выдаёт доступ сам по себе. Alitos при каждом запросе проверяет состояние пользователя и его текущий доступ к кабинету.

Если allowlist не задан, credential действует на все кабинеты, доступные пользователю в момент запроса. Если задан — только на перечисленные wrUserId.

REST JWT нельзя использовать для MCP. Пользователь, от имени которого выпущен credential, определяется токеном и не передаётся в request.

Карта API

Все пути в таблице относятся к https://api.alitos.io/v1/external.

Сценарий

Основные endpoints

Scope

Возможности

GET /capabilities

Любой выданный scope

Discovery

GET /namespaces, /cabinets, /cabinet-filters, /cabinet-filter-values, /partners/resolve; POST /cabinets/search

cabinet:read

Cabinet reads

GET /cabinets/{wrUserId}/context, /cabinets/{wrUserId}/settings, /cabinets/{wrUserId}/analytics-config, /cabinets/{wrUserId}/projects/catalog, /cabinets/{wrUserId}/projects/sources, /cabinets/{wrUserId}/audit/changes

cabinet:read

Analytics

POST /analytics/projects/query, /analytics/sources/jobs

Соответственно cabinet:read, cabinet:exports:run

Deals

POST /cabinets/{wrUserId}/deals/search, /cabinets/{wrUserId}/deals/export-jobs

Соответственно cabinet:deals:read, cabinet:exports:run

Projects

POST /cabinets/{wrUserId}/project-creation/preparations, /project-creation/preparations/{preparationId}/commit, /cabinets/{wrUserId}/projects/source-updates, /cabinets/{wrUserId}/projects/state-updates

cabinet:projects:write

Settings

POST /cabinets/{wrUserId}/settings/updates, /cabinets/{wrUserId}/analytics-config/updates

cabinet:settings:write

Operations

GET /operations/{operationId}, /operations/{operationId}/result

Scope исходной операции

Точные query parameters, request bodies и response schemas смотрите в OpenAPI.

Общие правила

  • Все 64-битные WR identifiers передаются JSON-строками.

  • Pagination использует opaque cursor. Не разбирайте и не изменяйте его.

  • Продолжайте pagination с теми же фильтрами и контекстом.

  • Для endpoints, создающих durable operation, передавайте reason и Idempotency-Key.

  • Требования к заголовкам и размерам payload задаёт OpenAPI.

Если кабинет доступен в нескольких контекстах, используйте namespaceId и placementId из discovery. Не меняйте их внутри pagination или idempotent flow.

Безопасные изменения

Idempotency

Для всех endpoints, создающих durable operation, обязателен Idempotency-Key. Используйте UUID или устойчивый идентификатор операции вашей системы.

Тот же key с тем же payload возвращает существующую операцию. Тот же key с другим payload или reason возвращает 409 idempotency_key_conflict.

Если HTTP-ответ потерян, повторите исходный запрос с тем же key. Не создавайте новый key только из-за timeout.

ETag

Settings, analytics config и источники проекта защищены ETag. Сначала выполните GET, затем передайте полученное значение в If-Match.

Strong ETag для источников возвращается при чтении одного существующего проекта. Aggregate response содержит только weak validator.

Отсутствующий If-Match возвращает 428, устаревший — 412. После 412 перечитайте ресурс и сформируйте изменение заново.

Weak ETag с префиксом W/ нельзя использовать для update.

Пример изменения settings

ETAG=$(
  curl --silent --include \
    'https://api.alitos.io/v1/external/cabinets/123456789/settings' \
    --header "Authorization: Bearer ${ALITOS_EXTERNAL_JWT}" \
  | awk -F': ' 'tolower($1)=="etag" {gsub("\r","",$2); print $2}'
)

curl --request POST \
  'https://api.alitos.io/v1/external/cabinets/123456789/settings/updates' \
  --header "Authorization: Bearer ${ALITOS_EXTERNAL_JWT}" \
  --header 'Content-Type: application/json' \
  --header "Idempotency-Key: ${CLIENT_OPERATION_ID}" \
  --header "If-Match: ${ETAG}" \
  --data '{
    "reason": "Включение B8 для nightly pipeline",
    "patch": {
      "connectProjectsB8": true
    }
  }'

Создание проектов

Создание проекта состоит из двух шагов:

  1. Создайте preparation для проверки данных.

  2. Выполните commit с reason и Idempotency-Key.

Preparation не меняет WR и действует 30 минут.

Если job завершился с ambiguous_upstream_outcome, сначала проверьте состояние проектов. Не запускайте автоматический повтор с новым key.

Асинхронные операции

Endpoints, создающие operation, возвращают 202 Accepted, operationId, Location и Retry-After. Project preparation возвращает 201 и job не создаёт.

Статусы операции: queued, running, succeeded, failed.

curl --request POST \
  'https://api.alitos.io/v1/external/analytics/sources/jobs' \
  --header "Authorization: Bearer ${ALITOS_EXTERNAL_JWT}" \
  --header 'Content-Type: application/json' \
  --header "Idempotency-Key: ${CLIENT_OPERATION_ID}" \
  --data '{
    "reason": "Еженедельный контроль источников",
    "wrUserIds": ["123456789"],
    "dateFrom": "2026-07-01",
    "dateTo": "2026-07-27",
    "metricPreset": "b4_source_quality",
    "includeSourceRows": true
  }'

curl \
  'https://api.alitos.io/v1/external/operations/${OPERATION_ID}' \
  --header "Authorization: Bearer ${ALITOS_EXTERNAL_JWT}"

curl \
  'https://api.alitos.io/v1/external/operations/${OPERATION_ID}/result?pageSize=250' \
  --header "Authorization: Bearer ${ALITOS_EXTERNAL_JWT}"

При polling соблюдайте Retry-After и добавляйте jitter. Не опрашивайте операцию чаще одного раза в две секунды.

Операция и результат доступны тому же пользователю и credential, которые создали job. Новый JWT из того же credential подходит для polling при наличии исходного scope.

Для завершившейся с ошибкой операции status endpoint возвращает HTTP 200 и status: "failed". Запрос /result до succeeded возвращает 409 operation_not_completed.

Большие результаты читаются страницами через opaque nextCursor.

Ошибки и повторные запросы

Cabinet API возвращает RFC 7807 Problem Details. Для логики используйте code и retryable, а traceId сохраняйте для диагностики.

Ответ

Действие клиента

401

Получить новый JWT; проверить resource

403

Проверить scope, allowlist и доступ пользователя

409

Проверить code; не менять idempotency key автоматически

412

Перечитать ресурс и повторно сформировать update

428

Добавить обязательный If-Match

429

Соблюдать Retry-After

503

Повторять только при retryable: true

Повторяйте запрос без изменения только при retryable: true. Для ambiguous_upstream_outcome автоматический retry запрещён.

OAuth endpoint возвращает стандартные error и error_description, а не Problem Details.

Лимиты

Актуальные лимиты возвращает /capabilities. Начальные значения: 300 GET/min, 60 POST/min и до 5 одновременных jobs на credential.

Idempotency keys, jobs и результаты хранятся 7 дней.

Срок, замена и отзыв

Плановая ротация credential не требуется. Заменяйте его при компрометации, смене владельца или по политике компании.

Ранее выпущенные credentials сохраняют заданный expiresAt.

Revocation сразу запрещает новый token exchange. Уже выпущенный JWT может действовать до 60 минут.

При компрометации отзовите credential и обратитесь к администратору для снятия external_api_access.

Не записывайте credential, JWT или чувствительные payload в логи и поле reason.

Совместимость

Внутри /v1 изменения совместимы и additive. Игнорируйте незнакомые response fields и не полагайтесь на порядок JSON properties.

Полный контракт: OpenAPI v1.