# Самостоятельная авторизация через curl

Source: <https://telegafirst.com/docs/curl-device-grant>
Locale: ru
releaseGitSha: c9a428d902de0b479a981e43774f38b9ec077ed7
sourceContentDigest: 34e281c48716f6ab8a7060dee508721a173ab67ce92a482102c1045bda7255dd
Version: 1

Этот сценарий регистрирует самостоятельный public diagnostic client, получает согласие человека в Telegram и сохраняет собственный access token локально. Нужны `curl`, `jq`, приватный локальный каталог и доступ человека к платформенному Telegram-боту. Бот вашего бизнеса для самого входа не требуется.

Используйте только регистрацию этого диагностического клиента. Токены из ChatGPT, Claude, Codex или другого AI-хоста не извлекают и не переиспользуют. Owner OAuth, API key и сессии покупателя — разные способы доступа; `create_session` и surface JWT не заменяют этот вход. Подробности — в [авторизации](https://telegafirst.com/docs/authorization).

Ниже приведены обезличенные контрактные ответы самостоятельного клиента. Это не запись ручного подключения AI-хоста; успешный curl не подтверждает поддержку или проверку какого-либо AI-клиента.

## Discovery и локальный каталог

Machine endpoint — `https://mcp.telegafirst.com/api/v1/mcp`. Публичная документация доступна на обеих зонах `.ru` и `.com`; домен документации не меняет её язык.

Выполняйте команды в одной локальной shell-сессии. Каталог получает права `700`, создаваемые файлы — `600` благодаря `umask 077`. Не включайте shell tracing и не передавайте содержимое секретных файлов в промпт, чат или публичный отчёт.

```bash
set -eu
umask 077
TGF_DIAGNOSTIC_DIR=$(mktemp -d "${TMPDIR:-/tmp}/tgf-device.XXXXXX")
chmod 700 "$TGF_DIAGNOSTIC_DIR"
TGF_OAUTH_BASE=https://mcp.telegafirst.com/api/v1/oauth
TGF_MCP_URL=https://mcp.telegafirst.com/api/v1/mcp

curl --silent --show-error --fail-with-body \
  --output "$TGF_DIAGNOSTIC_DIR/resource-metadata.json" \
  https://mcp.telegafirst.com/.well-known/oauth-protected-resource/api/v1/mcp
curl --silent --show-error --fail-with-body \
  --output "$TGF_DIAGNOSTIC_DIR/authorization-server.json" \
  https://mcp.telegafirst.com/.well-known/oauth-authorization-server
jq '{resource,authorization_servers}' "$TGF_DIAGNOSTIC_DIR/resource-metadata.json"
jq '{registration_endpoint,device_authorization_endpoint,token_endpoint,grant_types_supported}' \
  "$TGF_DIAGNOSTIC_DIR/authorization-server.json"
```

Проверьте resource и endpoints в возвращённой metadata: регистрация — `/api/v1/oauth/register`, device authorization — `/api/v1/oauth/device_authorization`, token — `/api/v1/oauth/token`. Поддержанный grant type — `urn:ietf:params:oauth:grant-type:device_code`. Если metadata другого сервера отличается, не отправляйте туда диагностические коды из этого сценария.

## Регистрация собственного public client

Запрос `POST /api/v1/oauth/register` использует JSON и `token_endpoint_auth_method: "none"`; client secret не нужен. Регистрация требует `redirect_uris`. Здесь используется разрешённый loopback URI; device grant открывает возвращённый Telegram-deeplink и не запускает локальный callback listener.

```json
{"client_name":"TelegaFirst curl diagnostic","redirect_uris":["http://127.0.0.1:8765/callback"],"token_endpoint_auth_method":"none"}
```

```bash
printf '%s\n' '{"client_name":"TelegaFirst curl diagnostic","redirect_uris":["http://127.0.0.1:8765/callback"],"token_endpoint_auth_method":"none"}' \
  > "$TGF_DIAGNOSTIC_DIR/register-request.json"
curl --silent --show-error --fail-with-body --request POST \
  --header 'Content-Type: application/json' \
  --data-binary @"$TGF_DIAGNOSTIC_DIR/register-request.json" \
  --output "$TGF_DIAGNOSTIC_DIR/register.json" "$TGF_OAUTH_BASE/register"
```

Обезличенный успешный ответ, HTTP `201`:

```json
{"client_id":"<CLIENT_ID>","client_name":"TelegaFirst curl diagnostic","redirect_uris":["http://127.0.0.1:8765/callback"],"token_endpoint_auth_method":"none","client_id_issued_at":1790769600}
```

Используйте полученный `client_id`, а не идентификатор зарегистрированного AI-хоста.

## Device authorization и согласие в Telegram

Полный запрос `POST /api/v1/oauth/device_authorization`:

```json
{"client_id":"<CLIENT_ID>","resource":"https://mcp.telegafirst.com/api/v1/mcp"}
```

```bash
jq --arg resource "$TGF_MCP_URL" '{client_id:.client_id,resource:$resource}' \
  "$TGF_DIAGNOSTIC_DIR/register.json" > "$TGF_DIAGNOSTIC_DIR/device-request.json"
curl --silent --show-error --fail-with-body --request POST \
  --header 'Content-Type: application/json' \
  --data-binary @"$TGF_DIAGNOSTIC_DIR/device-request.json" \
  --output "$TGF_DIAGNOSTIC_DIR/device.json" "$TGF_OAUTH_BASE/device_authorization"
jq '{verification_uri_complete,user_code,expires_in,interval}' "$TGF_DIAGNOSTIC_DIR/device.json"
```

Обезличенный успешный ответ, HTTP `200`:

```json
{"device_code":"<DEVICE_CODE>","user_code":"<USER_CODE>","verification_uri":"https://t.me/platform_bot","verification_uri_complete":"https://t.me/platform_bot?start=s_<NONCE>","expires_in":300,"interval":5}
```

`platform_bot` в этом ответе — имя бота тестового fixture. В реальном запуске откройте **возвращённый сервером** `verification_uri_complete`; не подставляйте тестовое имя или другой nonce. Ссылка содержит одноразовый код входа: открывайте её локально и не публикуйте.

Человек проверяет имя `TelegaFirst curl diagnostic`, совпадение `user_code` и каждое показанное право с обозначением риска, затем подтверждает карточку в Telegram. Текущий device flow запрашивает все девять consent bundles, включая операции с деньгами. Здесь нет обещания выбрать более узкий пакет через параметр `scope`. Не подтверждайте карточку, если права не подходят вашей задаче.

Выданный `OAuthGrant` сохраняет точные expanded capability scopes принятого согласия. Поле `scope` ответа ниже перечисляет bundle IDs; оно не заменяет проверки текущей роли, доступа к бизнесу, тарифа и прав при каждом вызове. [Права и ограничения](https://telegafirst.com/docs/scopes-and-permissions).

## Polling: interval, slow\_down и expiry

Полный запрос `POST /api/v1/oauth/token`:

```json
{"grant_type":"urn:ietf:params:oauth:grant-type:device_code","client_id":"<CLIENT_ID>","device_code":"<DEVICE_CODE>"}
```

Первый poll и каждый последующий выполняются не раньше текущего `interval`. Начальное значение — 5 секунд. `authorization_pending` сохраняет интервал; каждый `slow_down` увеличивает его на 5 секунд для всех следующих попыток. Окно подтверждения — 300 секунд от device authorization; polling его не продлевает.

Сервер сообщает ожидание и слишком частый polling через HTTP `400`, поэтому token-запрос ниже сохраняет тело без `--fail-with-body` и отдельно проверяет HTTP status.

```json
{"error":"authorization_pending","error_description":"waiting for Telegram approval"}
```

```json
{"error":"slow_down","error_description":"increase the polling interval by five seconds"}
```

```bash
jq -s '{grant_type:"urn:ietf:params:oauth:grant-type:device_code",client_id:.[0].client_id,device_code:.[1].device_code}' \
  "$TGF_DIAGNOSTIC_DIR/register.json" "$TGF_DIAGNOSTIC_DIR/device.json" \
  > "$TGF_DIAGNOSTIC_DIR/token-request.json"
TGF_POLL_INTERVAL=$(jq -r '.interval' "$TGF_DIAGNOSTIC_DIR/device.json")
while :; do
  sleep "$TGF_POLL_INTERVAL"
  TGF_HTTP_STATUS=$(curl --silent --show-error --request POST \
    --header 'Content-Type: application/json' \
    --data-binary @"$TGF_DIAGNOSTIC_DIR/token-request.json" \
    --output "$TGF_DIAGNOSTIC_DIR/token.json" --write-out '%{http_code}' \
    "$TGF_OAUTH_BASE/token")
  if [ "$TGF_HTTP_STATUS" = 200 ]; then
    jq -e '.token_type == "Bearer" and (.access_token | type == "string")' \
      "$TGF_DIAGNOSTIC_DIR/token.json" >/dev/null
    break
  fi
  TGF_OAUTH_ERROR=$(jq -r '.error // "unexpected_response"' "$TGF_DIAGNOSTIC_DIR/token.json")
  case "$TGF_OAUTH_ERROR" in
    authorization_pending) ;;
    slow_down) TGF_POLL_INTERVAL=$((TGF_POLL_INTERVAL + 5)) ;;
    *) printf '%s\n' 'Authorization stopped; inspect the private response locally and restart authorization if needed.' >&2; exit 1 ;;
  esac
done
chmod 600 "$TGF_DIAGNOSTIC_DIR"/*.json
```

Успех, HTTP `200`; токен здесь заменён placeholder:

```json
{"access_token":"<ACCESS_TOKEN>","token_type":"Bearer","scope":"workspace-read customer-conversations-read bot-setup conversations storefront-and-marketing ai-and-knowledge access-and-integrations platform-support money-operations"}
```

Текущий device token response не содержит `refresh_token` или `expires_in`. Значение 300 относится к **окну device authorization**, а не к сроку жизни access token. После успеха прекратите polling: authorization уже потреблена.

Терминальные ошибки token endpoint, HTTP `400`:

| Ситуация                          | Полный ответ                                                                                         | Действие                                                                  |
| --------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Человек отказал                   | `{"error":"access_denied","error_description":"the user denied authorization"}`                      | Остановить polling; учитывать решение человека.                           |
| Device code истёк                 | `{"error":"expired_token","error_description":"device authorization expired"}`                       | Получить новую device authorization и новое согласие.                     |
| Сессия истекла или уже потреблена | `{"error":"expired_token","error_description":"device authorization expired or was consumed"}`       | Не повторять обмен после успеха; для нового входа начать новое согласие.  |
| Чужой client\_id                  | `{"error":"invalid_grant","error_description":"device authorization belongs to a different client"}` | Проверить локальную пару registration/device; не использовать чужие коды. |

При любой другой ошибке цикл также завершается; ответ остаётся в приватном файле. Device authorization до создания кодов отклоняет неизвестный client или неверный resource, HTTP `400`:

```json
{"error":"invalid_client","error_description":"unknown client_id"}
```

```json
{"error":"invalid_target","error_description":"resource does not match this authorization server"}
```

## Локальный токен и JSON-RPC curl

Создайте приватный файл заголовка из **своего** token response. Токен не появится в аргументах curl, shell history или выводе команды:

```bash
jq -r '"Authorization: Bearer " + .access_token' "$TGF_DIAGNOSTIC_DIR/token.json" \
  > "$TGF_DIAGNOSTIC_DIR/authorization-header.txt"
chmod 600 "$TGF_DIAGNOSTIC_DIR/authorization-header.txt"
```

MCP использует Streamable HTTP на том же endpoint. У `tools/list` и `describe_tool` нет самостоятельного REST twin в этом сценарии; вызывайте их JSON-RPC. Эти примеры читают список и схему, не создают бизнес и не меняют деньги. Доступ требует действующего OAuth grant; meta-discovery не требует отдельного operation scope, но показывает только допустимый текущему caller inventory.

Для поддержанного legacy handshake отправьте `initialize`, затем прочитайте актуальный список:

```bash
printf '%s\n' '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {},
    "clientInfo": {
      "name": "TelegaFirst curl diagnostic",
      "version": "1.0"
    }
  }
}' \
  > "$TGF_DIAGNOSTIC_DIR/initialize-request.json"
curl --silent --show-error --fail-with-body --request POST \
  --header @"$TGF_DIAGNOSTIC_DIR/authorization-header.txt" \
  --header 'Content-Type: application/json' --header 'Accept: application/json, text/event-stream' \
  --data-binary @"$TGF_DIAGNOSTIC_DIR/initialize-request.json" \
  --output "$TGF_DIAGNOSTIC_DIR/initialize-response.txt" "$TGF_MCP_URL"

printf '%s\n' '{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}' \
  > "$TGF_DIAGNOSTIC_DIR/tools-list-request.json"
curl --silent --show-error --fail-with-body --request POST \
  --header @"$TGF_DIAGNOSTIC_DIR/authorization-header.txt" \
  --header 'Content-Type: application/json' --header 'Accept: application/json, text/event-stream' \
  --data-binary @"$TGF_DIAGNOSTIC_DIR/tools-list-request.json" \
  --output "$TGF_DIAGNOSTIC_DIR/tools-list-response.txt" "$TGF_MCP_URL"
```

В stateless transport не нужно переносить session ID между этими запросами. Сохраняйте ответ как текст: HTTP transport может вернуть JSON или SSE; не считайте весь SSE-файл одним JSON-документом. Успешный JSON-RPC ответ имеет соответствующий `id` и `result`; для `tools/list` прочитайте возвращённые `tools` и их schemas, а не фиксированное число инструментов из примера.

Чтобы получить схему конкретного доступного инструмента, вызовите `describe_tool` с `tool_id`, например:

```json
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"describe_tool","arguments":{"tool_id":"get_onboarding_state"}}}
```

```bash
printf '%s\n' '{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "describe_tool",
    "arguments": {
      "tool_id": "get_onboarding_state"
    }
  }
}' \
  > "$TGF_DIAGNOSTIC_DIR/describe-request.json"
curl --silent --show-error --fail-with-body --request POST \
  --header @"$TGF_DIAGNOSTIC_DIR/authorization-header.txt" \
  --header 'Content-Type: application/json' --header 'Accept: application/json, text/event-stream' \
  --data-binary @"$TGF_DIAGNOSTIC_DIR/describe-request.json" \
  --output "$TGF_DIAGNOSTIC_DIR/describe-response.txt" "$TGF_MCP_URL"
```

Полученная схема не устанавливает dedicated callable tool и не доказывает разрешение выполнить операцию. Проверяйте `tools/list` после изменения readiness или активного бизнеса; следующий шаг описан в [руководстве по началу работы](https://telegafirst.com/docs/getting-started) и [MCP-гайде](https://telegafirst.com/docs/mcp-guide).

HTTP `401` с `WWW-Authenticate` требует проверить или повторить авторизацию. Ошибка выполнения инструмента может прийти в HTTP-успешном JSON-RPC result с `isError: true`; наличие HTTP `200` само по себе не означает успех инструмента. Отказ прав, тарифного окна или readiness не обходят API key либо surface JWT. Здесь не приведён выдуманный MCP success/failure fixture: actual result зависит от текущих прав и состояния вашего бизнеса. [Коды ошибок](https://telegafirst.com/docs/errors).

## Завершение диагностики

Файлы `token.json`, `authorization-header.txt`, device request/response и ответы MCP остаются только локально. Не добавляйте их в репозиторий, evidence, URL или промпт. После завершения удалите созданный приватный каталог; удаление файлов само по себе **не отзывает** уже выданный grant. Для прекращения доступа отзовите подключение через существующее управление доступом, описанное в [авторизации](https://telegafirst.com/docs/authorization).
