Перейти к документации
ДокументацияREST API

REST API

Управляйте агентами, ключами, доступом и журналом из скрипта с ключом консоли.

На этой странице

Адрес и ключ

Скрипты обращаются к https://mcp.sallyport.cloud/v1 с ключом консоли. Его создаёт владелец в разделе «Настройки», «Ключи API» кнопкой «Новый ключ API». Ключ начинается с sp_ck_ и показывается один раз.

Передавайте его как Authorization: Bearer $SP_CONSOLE_KEY. Ключ действует как владелец всей организации. Он перестаёт работать, когда его отзывают или когда создавший его владелец перестаёт быть владельцем.

Несколько действий требуют человека, вошедшего в консоль, и на ключ консоли отвечают 403. Это создание ключей консоли, ответ на подтверждения, вход через OAuth, настройки уведомлений, отметка сигналов и скачивание сохранённых ответов.

Токены агентов (sp_live_…, sp_test_…) нужны для /mcp и /p/. В /v1 они открывают один маршрут: GET /v1/bodies/{body_ref} для вызовов самого агента.

Shell · организация и её рабочие пространства
export SP_CONSOLE_KEY='sp_ck_…'

curl --fail-with-body \
  --header "Authorization: Bearer $SP_CONSOLE_KEY" \
  https://mcp.sallyport.cloud/v1/tenants/me

curl --fail-with-body \
  --header "Authorization: Bearer $SP_CONSOLE_KEY" \
  https://mcp.sallyport.cloud/v1/workspaces

Ответы и ошибки

Успешный ответ выглядит как {"data": …, "request_id": "…"} со статусом 200 или 201, если что-то создано. Тела запросов в JSON до 1 МиБ. Неизвестные маршруту поля пропускаются, кроме снятых полей правил вроде class_policy на /bindings: на них ответ 422.

Ошибка на всех маршрутах устроена одинаково. В message короткая фраза на английском, в details данные для программы.

JSON · 422 от POST /v1/agents
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Validation failed",
    "details": {
      "fields": [
        { "field": "workspace_id", "code": "not_a_uuid", "message": "Select a workspace" }
      ]
    },
    "request_id": "9e8df29b6763e0ddd8138df2790de68a"
  }
}
Статус и кодЧто значит
401 AUTH_FAILEDКлюча нет, он неверный или отозван.
403 FORBIDDENВашей роли не хватает. Нужная роль названа в details.required_role.
404 NOT_FOUND, 404 CALL_NOT_FOUNDВ вашей организации нет объекта с таким ID, или ID не является UUID.
409 CONFLICT, 409 CALL_CONFLICTОбъект занят, изменился или уже в этом состоянии. Подробности в details.
410 APPROVAL_EXPIREDКарточка подтверждения закрылась раньше ответа.
422 VALIDATION_FAILEDПоле заполнено неверно. details.fields перечисляет каждое с field, code и message.
400 BAD_REQUESTПараметр запроса, которого маршрут не принимает. Допустимые перечислены в details.accepted.
503 UNAVAILABLE, 500 INTERNALСбой на стороне Sallyport. После 503 повторите через details.retry_after_ms.

Списки

Большинство списков отдаёт все строки сразу: в data лежит {"items": […], "total": n, "page": 1, "limit": n}.

Вызовы, подтверждения и события аудита приходят страницами вида {"items", "next_cursor", "total", "truncated"}. limit по умолчанию 50, самое большее 200. Передавайте next_cursor обратно в cursor, пока он не станет null.

Фильтр с несколькими значениями принимает их через запятую, например state=completed,failed. Большинство маршрутов списков отклоняет незнакомый параметр, так что опечатка в фильтре не вернёт всё подряд.

Заведите скрипту своего агента

POST /agents принимает name и workspace_id. environment бывает live (по умолчанию) или test; description, delegate_user_id и allowed_ips необязательны. workspace_id возьмите из GET /workspaces.

Новый агент сразу получает все активные подключения своего пространства и среды, и все они работают без вопросов. Затем выпустите ему токен. В примере ID нового агента читается через jq.

Shell · агент и его токен
export SP_WORKSPACE_ID='…'

AGENT_ID=$(curl --fail-with-body --silent \
  --request POST \
  --header "Authorization: Bearer $SP_CONSOLE_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"name\":\"Nightly report\",\"workspace_id\":\"$SP_WORKSPACE_ID\"}" \
  https://mcp.sallyport.cloud/v1/agents | jq -r .data.id)

curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $SP_CONSOLE_KEY" \
  --header "Content-Type: application/json" \
  --data '{"label":"CI"}' \
  "https://mcp.sallyport.cloud/v1/agents/$AGENT_ID/tokens"

Токен лежит в data.token, и больше нигде он не появится. У агента среды live он начинается с sp_live_, у тестового с sp_test_.

label до 100 символов, expires_at необязателен и задаётся временем RFC 3339 в будущем. DELETE /agents/{id}/tokens/{token_id} отзывает токен.

Добавьте ключ

POST /credentials сохраняет ключ и тут же создаёт его подключения. Все агенты того же пространства и среды получают их сразу.

Shell · ключ GitHub
export GITHUB_TOKEN='…'

curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $SP_CONSOLE_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"name\":\"GitHub\",\"workspace_id\":\"$SP_WORKSPACE_ID\",\"recipe_key\":\"github\",\"fields\":{\"value\":\"$GITHUB_TOKEN\"}}" \
  https://mcp.sallyport.cloud/v1/credentials

recipe_key называет сервис, список даёт GET /recipes. fields несёт секрет под именами из credential_fields рецепта; у большинства сервисов одно поле value. environment по умолчанию live.

В ответе описан ключ без секрета. data.used_by.bindings перечисляет новые подключения: id оттуда и есть {binding_id} для HTTP-прокси.

Ни один маршрут не возвращает секрет. Чтобы заменить его, отправьте новый объект fields в POST /credentials/{id}/rotate.

Настройте, как агент пользуется подключением

У каждой пары агента и подключения есть правило. class_policy задаёт для read, write и destructive одно из auto, ask или deny, новая пара начинает со всех трёх на auto.

Shell · спрашивать человека на каждый вызов
export SP_BINDING_ID='…'

curl --fail-with-body \
  --request PUT \
  --header "Authorization: Bearer $SP_CONSOLE_KEY" \
  --header "Content-Type: application/json" \
  --data '{"class_policy":{"read":"ask","write":"ask","destructive":"ask"}}' \
  "https://mcp.sallyport.cloud/v1/agents/$AGENT_ID/bindings/$SP_BINDING_ID"

PUT заменяет правило целиком. Класс, который вы не указали, становится ask, а неуказанные step_up, path_prefix_override, methods_override и rate_per_hour сбрасываются. Передавайте те, что хотите сохранить.

Необязательные поля: step_up (none или totp, код из аутентификатора при подтверждении необратимого вызова), path_prefix_override, methods_override и rate_per_hour. Последние три могут только сузить подключение.

DELETE /agents/{id}/bindings/{binding_id} забирает подключение у агента. POST /agents/{id}/bindings возвращает подключения: binding_ids или "all": true, по желанию с rule того же вида.

Читайте вызовы и журнал

GET /calls отдаёт вызовы страницами, новые первыми. Фильтры: workspace_id, agent_id, binding_id, state, class, decision, channel, host, q, session, from и to.

У вызова есть call_id, agent, binding, class, decision (run, ask или deny), state, summary, upstream_status, duration_ms, счётчики байтов, error_code и body_ref.

Состояния: admitted, awaiting_approval, dispatching, executing, completed, denied, failed, outcome_unknown и cancelled. GET /calls/{call_id} добавляет intent, ход вызова и номера его записей в журнале.

GET /audit/events отдаёт журнал страницами, новые записи первыми. GET /audit/export выгружает цепочку файлом JSON Lines: строка заголовка, затем по событию в строке. from_seq и to_seq задают диапазон.

Ограничьте агента известными адресами

allowed_ips в POST /agents и PATCH /agents/{id} принимает до 100 IP-адресов или CIDR-сетей. Пустой список разрешает любой адрес, а PATCH без этого поля оставляет текущий список.

Shell · разрешить один адрес
export SP_AGENT_IP='…'

curl --fail-with-body \
  --request PATCH \
  --header "Authorization: Bearer $SP_CONSOLE_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"allowed_ips\":[\"$SP_AGENT_IP\"]}" \
  "https://mcp.sallyport.cloud/v1/agents/$AGENT_ID"

SP_AGENT_IP это адрес, с которого приходят запросы агента. GET /me/ip, выполненный на той же машине, покажет адрес, который видит Sallyport.

Запрос с любого другого адреса получит 403 SOURCE_IP_NOT_ALLOWED на /mcp, /p/ и /v1/bodies. Уже идущие вызовы не прерываются.

Подключённые приложения

GET /oauth/consents возвращает ИИ-клиенты, вошедшие через OAuth, по одной строке на человека, клиента и агента. Участник получает свои строки, владелец, администратор или ключ консоли получают все.

В строке есть id, client_id, client_name, registration (cimd для клиента, которого называет его сайт, dcr для зарегистрировавшегося самостоятельно), verified, domain, publisher, redirect (kind: web, app или loopback, и host), agent (id, name), workspace_id, connected_by (id, name, email), connected_at, renewed_at, last_used_at и mine.

DELETE /oauth/consents/{id} отключает подключение целиком. Его вызовы сразу прекращаются на всех компьютерах, с которых входил клиент, и чтобы вернуться, клиент входит заново.

Экран согласия читает GET /oauth/consent?request=…, только для человека, вошедшего в консоль. Кроме ID клиента и адреса возврата там есть client.verified, client.domain, client.publisher, redirect (kind, host) и workspaces. У каждого рабочего пространства есть access: grants (existing, all или none) и connections, которыми будет пользоваться агент, каждое с id, name, channel и asks_first.

В строке есть и sub_agents: агенты, которых клиент создал для своих машин, каждый с id, name и box, машиной, для которой он создан. В GET /agents у такого агента есть created_by_client с client_id, client_name и box. parent_agent_id у него есть, только если подключение само агент, со scope=mcp agents. У остальных агентов в обоих полях null. Строка с manages_agents: true означает подключение платформы: оно управляет агентами и само не агент.

Токены для машин платформы

Платформа, которая запускает агентов на своих машинах, входит один раз со scope=agents. Это согласие даёт доступ к управлению агентами и не создаёт агента: человек видит короткую карточку без списка сервисов. С access-токеном подключения она создаёт агента для каждой машины в POST https://mcp.sallyport.cloud/v1/agents. Агент начинает без доступа: чем ему пользоваться, человек выбирает на его странице в консоли.

Shell · агент для одной машины
curl -sS https://mcp.sallyport.cloud/v1/agents \
  -H "Authorization: Bearer $SP_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "api-worker", "box": "5f0c2d1e-9a4b-4c1e-8f3a-2b7d9e6c1a40"}'

В ответе agent_id и access_url, страница доступа агента. Добавьте &return_to= со своей страницей, и человек вернётся одной кнопкой. У машины один агент: второй POST отвечает 409 с box_has_agent. GET /v1/agents показывает агентов человека с created_by_client, box и paused. PATCH и DELETE /v1/agents/{id} переименовывают, ставят на паузу или удаляют только агентов, которых создал этот клиент. Ни один вызов этого API не даёт и не забирает доступ. Передайте allowed_ips в POST или PATCH, и токены агента заработают только с этих адресов: токен, скопированный с машины, бесполезен где-либо ещё. Список, который человек задал в консоли, действует поверх.

За токеном машины платформа обменивает access-токен подключения в POST https://mcp.sallyport.cloud/token (RFC 8693) и называет агента в sallyport_agent. Токен живёт час.

Shell · токен для одной машины
curl -sS https://mcp.sallyport.cloud/token \
  -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
  -d client_id=https://detachbox.com/.well-known/sallyport-client.json \
  -d subject_token="$SP_ACCESS_TOKEN" \
  -d subject_token_type=urn:ietf:params:oauth:token-type:access_token \
  -d sallyport_agent="$AGENT_ID"

В ответе access_token, expires_in, sallyport_agent_id, sallyport_agent_name и sallyport_agent_url. Refresh-токена нет: платформа обменивает снова. В отказе есть sallyport_reason: subject_token_expired, consent_ended, agents_scope_missing, agent_not_bound, agent_deleted или agent_paused.

POST https://mcp.sallyport.cloud/revoke (RFC 7009) принимает token и client_id. Токен машины закрывает все токены этой машины. Refresh-токен закрывает всё подключение, как «Отключить».

GET https://mcp.sallyport.cloud/v1/access с Authorization: Bearer отвечает за агента этого токена, агента машины: services, число подключений, которыми он может пользоваться, access_url, его страница доступа, и connections, те же записи, что у list_access. Пока сервисов нет, message говорит об этом одной строкой. Токен, который не подходит, получает 401 с WWW-Authenticate: Bearer и error="invalid_token". Собственный токен подключения получает services: 0 и строку о том, что подключение ничем не пользуется. На /mcp и /p/ он получает 403 с error="insufficient_scope" и scope="mcp".

Готовые MCP-серверы

GET /v1/mcp-servers отдаёт удалённые MCP-серверы, которые консоль предлагает при добавлении подключения. Это серверы самих поставщиков, у каждого адрес и способ входа. Читать список может любой участник. Выбор сервера только заполняет форму, а сохраняется обычное подключение к удалённому MCP-серверу, см. MCP-клиенты и удалённые серверы.

JSON · GET /v1/mcp-servers, показан один элемент
{
  "data": {
    "items": [
      {
        "key": "datadog-site",
        "name": "Datadog (other sites)",
        "vendor": "Datadog",
        "url": "https://mcp.{site}/v1/mcp",
        "url_part": {
          "name": "site",
          "label": { "en": "Your Datadog site domain, for example datadoghq.eu or us5.datadoghq.com", "ru": "…", "es": "…", "pt": "…", "fr": "…", "de": "…" },
          "example": "datadoghq.eu"
        },
        "auth": "oauth",
        "dynamic_registration": true,
        "bearer_alternative": false,
        "api_key_header": null,
        "api_key_prefix": null,
        "key_url": null,
        "recipe": "datadog",
        "docs_url": "https://docs.datadoghq.com/mcp_server/setup/",
        "description": { "en": "Query logs, metrics, traces, monitors and incidents on your Datadog site.", "ru": "…", "es": "…", "pt": "…", "fr": "…", "de": "…" }
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 1
  },
  "request_id": "5d0c2f7e8a1b4c6d9e0f1a2b3c4d5e6f"
}
ПолеЧто значит
url, url_partАдрес сервера. Если url_part не null, в адресе стоит {name}. Эта часть у каждого клиента своя: label спрашивает её на шести языках, example показывает пример.
authoauth означает вход у поставщика, bearer принимает токен, выпущенный там, api_key отправляет ключ в заголовке api_key_header после api_key_prefix, none не требует ничего.
dynamic_registrationПри входе через OAuth поставщик сам регистрирует Sallyport. false значит, что вы вводите ID клиента, созданный у поставщика.
bearer_alternative, key_urlOAuth-сервер принимает и токен, выпущенный у поставщика, и страница, где выпускают этот токен или ключ.
recipeСервис каталога того же поставщика или null.
description, docs_urlЧто делает сервер, по одному предложению на шести языках, и страница поставщика об этом сервере.

Все маршруты

Пути указаны относительно https://mcp.sallyport.cloud/v1. «Участник» значит любую роль в организации. Ключ консоли считается владельцем.

МаршрутыКтоЗачем
GET /tenants/me, PATCH /tenants/meучастник, владелецОрганизация: название, настройки, счётчики.
POST /tenants/me/pause, POST /tenants/me/resumeвладелецОстановить и снова запустить все вызовы агентов. Для паузы нужен reason.
GET /me/ipлюбойАдрес, который Sallyport видит у этого клиента.
GET, POST /workspaces; GET, PATCH /workspaces/{id}участник, администраторРабочие пространства. Создаются с slug, name и kind (team или client).
GET /workspaces/{id}/report?period=YYYY-MMаудиторМесяц работы пространства: итоги, сервисы, необратимые вызовы.
GET, POST /agents; GET, PATCH, DELETE /agents/{id}участник, администраторАгенты. Удаление отвечает 409, пока у агента идёт вызов.
POST /agents/{id}/pause, POST /agents/{id}/resumeадминистраторОстановить одного агента и запустить снова. Для паузы нужен reason.
GET, POST /agents/{id}/tokens; DELETE /agents/{id}/tokens/{token_id}администраторТокены агента.
GET, POST /agents/{id}/bindings; PUT, DELETE /agents/{id}/bindings/{binding_id}участник, администраторПодключения агента и его правило на каждом.
GET, PUT /agents/{id}/limitsучастник, администраторconcurrency_limit, rate_per_hour и read_budget.multiplier.
GET, POST /credentials; GET, PATCH, DELETE /credentials/{id}администраторКлючи сервисов. Удаление ключа удаляет его подключения.
POST /credentials/{id}/rotate, PATCH /credentials/{id}/origin, GET /credentials/{id}/usage, POST /credentials/{id}/oauth/startадминистраторЗаменить секрет, отметить, отозван ли исходный ключ у поставщика, узнать, кто пользуется ключом. oauth/start начинает вход у поставщика, для него нужен человек, вошедший в консоль.
GET, POST /bindings; GET, PATCH, DELETE /bindings/{id}участник, администраторПодключения. PATCH сверяет If-Match с config_version, если заголовок передан.
POST /bindings/{id}/pause, /resume, /check-key, /oauth/startадминистраторОстановить подключение, запустить снова, заново проверить ключ. oauth/start выполняет вход в удалённый MCP-сервер, для него нужен человек, вошедший в консоль.
GET, POST /ssh-hosts; GET, PATCH, DELETE /ssh-hosts/{id}администраторСерверы для SSH-команд.
POST /database-connections/testадминистраторПроверить адрес и пароль базы данных до сохранения.
POST /email-connections/testадминистраторПроверить почтовый ящик и его пароль до сохранения.
GET /policies/rule-changes, POST /policies/rule-changes/{id}/revertучастник, администраторИстория изменений правил и отмена одного из них.
GET /recipes, GET /recipes/{key}участникКаталог сервисов, см. Рецепты.
GET /mcp-serversучастникГотовые удалённые MCP-серверы для нового подключения.
GET, POST /grants; POST /grants/{id}/revokeучастник, администраторРазрешения агенту на подключение с ограниченным сроком. Отозвать может и подтверждающий.
GET /approvals, GET /approvals/{call_id}, GET /approvals/{call_id}/payloadподтверждающийКарточки подтверждения и точный запрос за каждой. Список может читать и аудитор.
POST /approvals/{call_id}/open, /decide, /transferподтверждающийПродлить карточку, ответить на неё, передать другому. Ответить может только человек, вошедший в консоль.
GET /access-requests, GET /access-requests/{request_id}участникЗапросы агентов на сервисы, которыми они не смогли воспользоваться.
POST /access-requests/{request_id}/provision, /declineадминистраторОтветить на запрос ключом или отказать с reason.
GET /calls, GET /calls/live, GET /calls/{call_id}аудиторИстория вызовов, идущие вызовы, один вызов.
POST /calls/{call_id}/cancelадминистраторОстановить незавершённый вызов.
GET /bodies/{body_ref}/meta, GET /bodies/{body_ref}аудитор; скачивание ещё и сам агентРазмер и срок хранения сохранённого ответа и сам ответ. Скачивание понимает Range.
GET /audit/events, GET /audit/exportаудиторЖурнал страницами или одним файлом JSON Lines. Нужна роль на всю организацию, роли в одном рабочем пространстве мало.
GET /signals, POST /signals/{id}/ackучастник, администраторНеобычная активность и отметка «просмотрено».
GET /reports, GET /reports/{id}, GET /insightsучастникЕженедельные отчёты и статистика подтверждений.
GET /billing, POST /billing/planадминистратор, владелецТариф и расход, запрос другого тарифа.
GET, POST /webhooks; PATCH, DELETE /webhooks/{id}администраторСобытия журнала на ваш HTTPS-адрес. Секрет подписи показывается один раз.
GET /webhooks/{id}/deliveries, POST /webhooks/{id}/deliveries/{delivery_id}/retryадминистраторИстория доставок и повторная отправка неудачной.
GET /members, PATCH /members/{user_id}участник, администраторЛюди и их роли.
GET, POST /members/invitations; DELETE /members/invitations/{id}администраторПриглашения по почте.
GET /members/{user_id}/offboarding, POST /members/{user_id}/offboardадминистраторПередать дела уходящего человека.
GET, POST /guests; POST /guests/{id}/relink; DELETE /guests/{id}администраторПодтверждающие со стороны клиента, которые входят по ссылке из письма.
GET /onboarding, POST /onboarding/steps/{key}участникСписок первых шагов.
GET, POST /console-keys; DELETE /console-keys/{id}владелецКлючи консоли. Создать ключ может только владелец, вошедший в консоль.
GET /oauth/consents, DELETE /oauth/consents/{id}участник, администраторПодключённые приложения и отключение одного из них. Участник работает со своими, администратор со всеми.
/notifications/…, /oauth/consent, /guest/step-upчеловекЭкраны консоли для человека, вошедшего в неё.