OAuth
Дайте людям входить в ваш клиент через аккаунт Atli и действуйте от их имени по стандартному OAuth 2.1: discovery, PKCE, ротация refresh-токенов и отзыв доступа. Подходит любому клиенту: приложению, боту, сайту, серверу или ИИ-ассистенту.
Когда это нужно
ИИ-ассистенты — один из таких клиентов. Подключаясь к MCP-серверу Atli, они сами выполняют вход людей таким способом, так что для них ничего строить не нужно. Один и тот же поток подходит любому клиенту.
Любой клиент, действующий от имени человека: мобильное приложение, сайт, бот или сервер, который показывает человеку его eSIM или покупает eSIM для него, с его собственным аккаунтом Atli. Зарегистрируйте свой клиент, и его токены будут работать с API Atli.
Люди, вошедшие через принадлежащий вам клиент или по вашему партнёрскому адресу MCP-сервера, могут закрепляться за вами как ваши рефералы. По умолчанию это отключено, и Atli может включить это для вашего аккаунта. В любом случае ваш клиент может сам закрепить за вами приведённых им людей: см. раздел «Закрепите за собой приведённых людей» ниже.
Подключиться может только аккаунт, в который можно снова войти где-то ещё: с email, в том числе через Google, Apple или Facebook, либо с Telegram или WhatsApp.
Как это работает
Сервер авторизации — API Atli. Вход и экран согласия находятся на heyatli.com, по адресу /connect, поэтому ваше приложение никогда не видит пароль. Берите все эндпоинты из метаданных, а не прописывайте их вручную. Адреса на этой странице меняются вместе с переключателем Песочница и Рабочая среда.
Зарегистрируйте клиент один раз. Затем для каждого человека: отправьте его на эндпоинт авторизации с PKCE challenge, получите код, с которым он вернётся, обменяйте его на токены и вызывайте API с access-токеном. Обновляйте его, когда он истекает, и отзывайте, когда человек выходит.
curl https://api.heyatli.com/.well-known/oauth-authorization-server
{
"issuer": "https://api.heyatli.com",
"authorization_endpoint": "https://api.heyatli.com/oauth2/authorize",
"token_endpoint": "https://api.heyatli.com/oauth2/token",
"revocation_endpoint": "https://api.heyatli.com/oauth2/revoke",
"registration_endpoint": "https://api.heyatli.com/oauth2/register",
"jwks_uri": "https://api.heyatli.com/oauth2/jwks",
"scopes_supported": ["api", "offline_access"],
"code_challenge_methods_supported": ["S256"],
"client_id_metadata_document_supported": true,
…
}Для чего токен
Каждый запрос указывает адрес, для которого предназначен его токен, в resource (RFC 8707), и токен работает только там. Адрес не из этого списка или несуществующий реферальный код отклоняются с invalid_target, так что угадать ни то, ни другое нельзя.
| Адрес | Кто может его запросить | Что делает токен |
|---|---|---|
API Atli https://api.heyatli.com | Только принадлежащий вам клиент | Bearer-токен для API, действующий от имени человека, как токен из |
MCP-сервер Atli https://mcp.heyatli.com | Любой клиент | Позволяет ассистенту действовать от имени человека через инструменты MCP. |
Ваш партнёрский адрес https://mcp.heyatli.com/r/YOUR_CODE | Любой клиент | То же самое, и человек может закрепиться за вами как ваш реферал, когда это включат для вашего аккаунта. |
Регистрация клиента
Есть три способа. Для своего приложения зарегистрируйте собственный клиент: только такой может запрашивать доступ к API Atli.
Собственный клиент
Войдя в свой аккаунт, вызовите
POST /user/oauth-clients. Можно иметь 10 клиентов. Выбирайтеpublicдля всего, что работает на чужом устройстве, например мобильного приложения или страницы в браузере пользователя, иconfidentialдля сервера, который может хранить секрет.client_secretконфиденциального клиента есть только в этом ответе и больше нигде. Если вы его потеряете, замените его черезPOST /user/oauth-clients/{id}/rotate-secret. У вашего аккаунта должен быть email, потому что клиенту нужен реферальный код; если у вас его ещё нет, он будет создан.curl https://api.heyatli.com/user/oauth-clients \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "Travel Bot", "redirect_uris": ["https://bot.example/oauth/callback"], "client_type": "confidential" }' { "client_id": "atli_client_7f3a9c2e…", "client_secret": "…", "client_type": "confidential", … }Динамическая регистрация
POST /oauth2/register(RFC 7591), без входа. Так регистрируется большинство ассистентов. Допускается 10 регистраций в час с одного адреса, а клиент, к которому так никто и не подключился, удаляется через 30 дней.curl https://api.heyatli.com/oauth2/register \ -H 'Content-Type: application/json' \ -d '{ "client_name": "Travel Bot", "redirect_uris": ["https://bot.example/oauth/callback"] }' { "client_id": "atli_client_…", "token_endpoint_auth_method": "none", … }Документ метаданных client ID
Используйте в качестве
client_idразмещённый у васhttps-адрес, по которому отдаётся JSON-документ с описанием клиента. Atli читает его, когда кто-то входит, так что регистрировать ничего не нужно. Спецификация MCP предпочитает этот способ динамической регистрации.// Served at https://bot.example/oauth/client.json { "client_id": "https://bot.example/oauth/client.json", "client_name": "Travel Bot", "redirect_uris": ["https://bot.example/oauth/callback"], "token_endpoint_auth_method": "none" }
Redirect URI сравниваются точно. Каждый из них —
httpsилиhttpнаlocalhost,127.0.0.1или[::1], где может отличаться только порт, и без фрагмента. У клиента их может быть до 10.
Вход человека
Поток authorization code с PKCE (S256), который используют все клиенты, публичные и конфиденциальные.
Создайте пару PKCE
Сохраните verifier для обмена кода, а его SHA-256 challenge отправьте с запросом. Для каждого входа создавайте новую пару и новый
state.const base64url = (bytes) => btoa(String.fromCharCode(...bytes)) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); const verifier = base64url(crypto.getRandomValues(new Uint8Array(32))); const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)); const challenge = base64url(new Uint8Array(digest)); const state = base64url(crypto.getRandomValues(new Uint8Array(16)));Отправьте человека на вход
Откройте эндпоинт авторизации в его браузере.
scope— этоapi, аoffline_accessдобавляет refresh-токен. Человек входит и даёт согласие на heyatli.com/connect, где видно название вашего клиента, куда он вернёт человека и станет ли тот чьим-то рефералом. Запрос действует 10 минут.const url = new URL('https://api.heyatli.com/oauth2/authorize'); url.search = new URLSearchParams({ response_type: 'code', client_id: CLIENT_ID, redirect_uri: 'https://bot.example/oauth/callback', scope: 'api offline_access', state, code_challenge: challenge, code_challenge_method: 'S256', // Atli's API, for a client you own; or the MCP server, https://mcp.heyatli.com resource: 'https://api.heyatli.com', }).toString(); window.location.assign(url);Примите код, с которым он вернётся
Человек возвращается на ваш redirect URI с
code, вашимstateиiss. Проверьте, чтоstateваш, аiss— это издатель. Если человек отказал, вместо этого придётerror=access_denied.https://bot.example/oauth/callback?code=4bd81f…&state=…&iss=https%3A%2F%2Fapi.heyatli.com https://bot.example/oauth/callback?error=access_denied&state=…&iss=https%3A%2F%2Fapi.heyatli.comОбменяйте код на токены
Отправьте код вместе с verifier, тем же
redirect_uriиresource, а также учётными данными клиента, если они есть. Код действует 60 секунд и срабатывает один раз: если отправить его дважды или с неверным verifier, доступ человека прекращается, ведь код мог попасть к кому-то другому.Access-токен действует
expires_inсекунд, то есть 15 минут. Вместе с ним всегда приходит refresh-токен.curl https://api.heyatli.com/oauth2/token \ -u 'CLIENT_ID:CLIENT_SECRET' \ -d grant_type=authorization_code \ -d code=THE_CODE \ -d code_verifier=THE_VERIFIER \ -d redirect_uri=https://bot.example/oauth/callback \ -d client_id=CLIENT_ID \ -d resource=https://api.heyatli.com { "access_token": "…", "token_type": "Bearer", "expires_in": 900, "refresh_token": "…", "scope": "api offline_access" }
Сохранение входа
Когда access-токен истечёт, запросите новый с помощью refresh-токена. Можно запросить более узкий scope, но никогда не более широкий.
Refresh-токен меняется при каждом использовании, поэтому каждый раз сохраняйте новый. Если ответ до вас не дошёл, повторная отправка старого токена в течение короткого времени вернёт тот же ответ. После этого старый токен означает, что его скопировали, и доступ человека прекращается.
curl https://api.heyatli.com/oauth2/token \
-u 'CLIENT_ID:CLIENT_SECRET' \
-d grant_type=refresh_token \
-d refresh_token=THE_REFRESH_TOKEN \
-d client_id=CLIENT_ID \
-d resource=https://api.heyatli.comИспользование токена
Отправляйте его как Authorization: Bearer ACCESS_TOKEN на адрес, для которого он выдан, и никуда больше.
Токен для API Atli действует от имени человека на всех маршрутах API, как токен из /login. Считайте его непрозрачным: его содержимое может меняться.
Токен для MCP-сервера — это JWT, подписанный EdDSA. Сервер проверяет его по ключам из /oauth2/jwks, по его kid, и проверяет, что его aud — это собственный адрес сервера. В нём нет email и других данных: только sub (id человека), scope, grant_id и время.
curl https://api.heyatli.com/esims \
-H 'Authorization: Bearer ACCESS_TOKEN'Закрепите за собой приведённых людей
Закрепление за вами людей, вошедших через ваш клиент или по вашему партнёрскому адресу, по умолчанию отключено, и Atli может включить его для вашего аккаунта. Включено оно или нет, ваш клиент может сам привязать вошедшего человека к вашему реферальному коду с помощью его access-токена: POST /user/referral/redeem с вашим code.
result сообщает, что произошло: ok — человек привязан, already — у него уже есть реферер, notnew — он уже покупал раньше, own — это ваш собственный код, notfound — такого кода нет. Привязать можно только человека без реферера, который ещё ничего не покупал, и только один раз.
curl https://api.heyatli.com/user/referral/redeem \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{ "code": "YOUR_CODE" }'
{
"success": true,
"result": "ok",
"message": "…",
"discount_percent": 10
}Выход и отзыв доступа
Когда человек выходит из вашего приложения, отзовите его refresh-токен. Это прекращает его доступ в вашем клиенте вместе со всеми токенами. Эндпоинт отвечает 200 даже на незнакомый токен, чтобы с его помощью нельзя было выяснить, какие токены существуют.
Люди также могут сами посмотреть подключённые приложения и отозвать у них доступ (GET /user/oauth-grants, DELETE /user/oauth-grants/{id}). После этого ваш клиент не сможет получить новый токен, поэтому считайте отклонённое обновление выходом.
curl https://api.heyatli.com/oauth2/revoke \
-u 'CLIENT_ID:CLIENT_SECRET' \
-d token=THE_REFRESH_TOKEN \
-d client_id=CLIENT_IDОшибки
Эндпоинты токена, отзыва и регистрации отвечают собственным форматом ошибки OAuth, {"error": "invalid_grant", "error_description": "…"}, а не обычным форматом API. Ориентируйтесь на error, а не на описание. Каждый ответ эндпоинта токена содержит Cache-Control: no-store.
invalid_grant: код уже использован или истёк, verifier неверный, либо refresh-токен использован повторно после льготного периода. Выполните вход человека заново.invalid_target:resourceне является адресом, для которого может быть токен, или указывает несуществующий реферальный код.invalid_client: учётные данные клиента неверны или отсутствуют. Любой тип клиента получает одну и ту же ошибку.invalid_scope: при обновлении запрошено больше, чем разрешил человек.