OAuth
Cho phép mọi người đăng nhập vào client của bạn bằng tài khoản Atli của họ, và thay mặt họ thao tác, với OAuth 2.1 tiêu chuẩn: discovery, PKCE, refresh token xoay vòng và thu hồi. Mọi client đều dùng được: ứng dụng, bot, trang web, máy chủ hay trợ lý AI.
Khi nào nên dùng
Trợ lý AI là một loại client như vậy. Khi kết nối với máy chủ MCP của Atli, chúng tự đăng nhập cho mọi người theo cách này, nên bạn không cần xây dựng gì cho chúng. Cùng một luồng phục vụ mọi client.
Bất kỳ client nào thay mặt một người: ứng dụng điện thoại, trang web, bot hoặc máy chủ cho ai đó xem eSIM của họ hoặc mua eSIM cho họ, bằng chính tài khoản Atli của họ. Hãy đăng ký một client của riêng bạn, và token của nó sẽ dùng được với API của Atli.
Những người đăng nhập qua client do bạn sở hữu, hoặc tại địa chỉ đối tác của bạn trên máy chủ MCP, có thể được liên kết với bạn như người được bạn giới thiệu. Tính năng này mặc định đang tắt, và Atli có thể bật nó cho tài khoản của bạn. Dù thế nào, client của bạn vẫn có thể tự ghi nhận những người nó mang đến: xem "Ghi nhận những người bạn mang đến" bên dưới.
Chỉ tài khoản mà người dùng có thể đăng nhập lại ở nơi khác mới kết nối được: tài khoản có email, kể cả qua Google, Apple hoặc Facebook, hoặc có Telegram hay WhatsApp.
Cách hoạt động
API của Atli là máy chủ ủy quyền. Màn hình đăng nhập và màn hình đồng ý nằm trên heyatli.com, tại /connect, nên ứng dụng của bạn không bao giờ thấy mật khẩu. Hãy đọc mọi endpoint từ metadata thay vì viết cứng vào code. Các địa chỉ ở đây thay đổi theo nút chuyển Sandbox và Production.
Đăng ký client một lần. Sau đó, với mỗi người: chuyển họ đến endpoint ủy quyền kèm PKCE challenge, nhận mã mà họ mang về, đổi mã đó lấy token, và gọi API bằng access token. Làm mới khi token hết hạn, và thu hồi khi họ đăng xuất.
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,
…
}Token dùng cho đâu
Mỗi yêu cầu nêu địa chỉ mà token của nó dành cho, trong resource (RFC 8707), và token chỉ dùng được ở đó. Địa chỉ không thuộc danh sách này, hoặc mã giới thiệu không tồn tại, sẽ bị từ chối với invalid_target, nên không thể đoán được cái nào.
| Địa chỉ | Ai có thể yêu cầu | Token làm gì |
|---|---|---|
API của Atli https://api.heyatli.com | Chỉ client do bạn sở hữu | Một bearer token cho API, hoạt động với tư cách người dùng, giống token từ |
Máy chủ MCP của Atli https://mcp.heyatli.com | Mọi client | Cho phép trợ lý thay mặt người dùng thao tác qua các công cụ MCP. |
Địa chỉ đối tác của bạn https://mcp.heyatli.com/r/YOUR_CODE | Mọi client | Tương tự, và người dùng có thể được liên kết với bạn như người được bạn giới thiệu, sau khi tính năng này được bật cho tài khoản của bạn. |
Đăng ký client
Có ba cách. Với ứng dụng của riêng bạn, hãy đăng ký một client của riêng bạn: đây là loại duy nhất có thể yêu cầu API của Atli.
Client của riêng bạn
Khi đã đăng nhập bằng tài khoản của bạn, gọi
POST /user/oauth-clients. Bạn có thể có 10 client. Chọnpubliccho mọi thứ chạy trên thiết bị của người khác, như ứng dụng điện thoại hay trang web trong trình duyệt của họ, vàconfidentialcho máy chủ có thể giữ bí mật.client_secretcủa client bảo mật chỉ có trong phản hồi này, không ở đâu khác. Nếu bạn làm mất, hãy thay bằngPOST /user/oauth-clients/{id}/rotate-secret. Tài khoản của bạn cần có email, vì client cần một mã giới thiệu; nếu bạn chưa có, mã sẽ được tạo cho bạn.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", … }Đăng ký động
POST /oauth2/register(RFC 7591), không cần đăng nhập. Đây là cách hầu hết các trợ lý đăng ký. Cho phép 10 lần đăng ký mỗi giờ từ một địa chỉ, và client không ai kết nối sẽ bị xóa sau 30 ngày.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", … }Tài liệu metadata client ID
Dùng một địa chỉ
httpsdo bạn lưu trữ làmclient_id, phục vụ một tài liệu JSON mô tả client. Atli đọc nó khi có người đăng nhập, nên không cần đăng ký gì cả. Đặc tả MCP ưu tiên cách này hơn đăng ký động.// 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 được so khớp chính xác. Mỗi URI là
https, hoặchttptrênlocalhost,127.0.0.1hay[::1], nơi chỉ cổng được phép khác, và không có fragment. Một client có thể có tối đa 10.
Đăng nhập cho một người
Luồng authorization code, với PKCE (S256), mà mọi client đều dùng, dù là công khai hay bảo mật.
Tạo cặp PKCE
Giữ verifier để dùng khi đổi mã, và gửi challenge SHA-256 của nó kèm yêu cầu. Tạo cặp mới, và
statemới, cho mỗi lần đăng nhập.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)));Chuyển họ đi đăng nhập
Mở endpoint ủy quyền trong trình duyệt của họ.
scopelàapi, vàoffline_accessthêm refresh token. Họ đăng nhập và đồng ý tại heyatli.com/connect, nơi hiển thị tên client của bạn, nơi nó đưa họ quay lại, và liệu họ có trở thành người được ai đó giới thiệu hay không. Yêu cầu có hiệu lực 10 phút.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);Nhận mã họ mang về
Họ quay lại redirect URI của bạn với
code,statecủa bạn vàiss. Kiểm tra rằngstatelà của bạn vàisslà bên phát hành. Nếu họ từ chối, kết quả sẽ mangerror=access_deniedthay vào đó.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Đổi mã lấy token
Gửi mã kèm verifier, cùng
redirect_urivàresourcenhư trước, và thông tin xác thực của client nếu có. Mã có hiệu lực 60 giây và chỉ dùng được một lần: gửi hai lần, hoặc với verifier sai, sẽ chấm dứt quyền truy cập của người đó, vì có thể người khác đang giữ mã.Access token có hiệu lực
expires_ingiây, tức 15 phút. Luôn có refresh token đi kèm.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" }
Duy trì đăng nhập
Khi access token hết hạn, hãy yêu cầu token mới bằng refresh token. Bạn có thể yêu cầu scope hẹp hơn, không bao giờ rộng hơn.
Refresh token thay đổi sau mỗi lần dùng, nên hãy lưu token mới mỗi lần. Nếu một phản hồi không đến được với bạn, gửi lại token cũ trong một khoảng ngắn sẽ nhận lại đúng phản hồi đó. Sau khoảng đó, token cũ nghĩa là nó đã bị sao chép, và quyền truy cập của người đó chấm dứt.
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.comDùng token
Gửi nó dưới dạng Authorization: Bearer ACCESS_TOKEN, đến đúng địa chỉ mà nó được cấp cho và không đến nơi nào khác.
Token cho API của Atli hoạt động với tư cách người dùng trên mọi route của API, như token từ /login. Hãy coi nó là không trong suốt: nội dung bên trong có thể thay đổi.
Token cho máy chủ MCP là một JWT được ký bằng EdDSA. Máy chủ kiểm tra nó với các khóa tại /oauth2/jwks, theo kid của nó, và kiểm tra rằng aud là địa chỉ của chính máy chủ. Nó không chứa email hay thông tin nào khác: chỉ có sub (id của người dùng), scope, grant_id và các mốc thời gian.
curl https://api.heyatli.com/esims \
-H 'Authorization: Bearer ACCESS_TOKEN'Ghi nhận những người bạn mang đến
Việc liên kết những người đăng nhập qua client của bạn, hoặc tại địa chỉ đối tác của bạn, với bạn mặc định đang tắt, và Atli có thể bật nó cho tài khoản của bạn. Dù có bật hay không, client của bạn vẫn có thể tự liên kết một người đã đăng nhập với mã giới thiệu của bạn, bằng access token của họ: POST /user/referral/redeem với code của bạn.
result cho biết điều gì đã xảy ra: ok là đã liên kết, already nghĩa là họ đã có người giới thiệu, notnew là họ đã từng mua hàng, own là đó là mã của chính bạn, và notfound là mã không tồn tại. Chỉ người chưa có người giới thiệu và chưa mua hàng mới được liên kết, và chỉ một lần.
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
}Đăng xuất và thu hồi quyền truy cập
Khi ai đó đăng xuất khỏi ứng dụng của bạn, hãy thu hồi refresh token của họ. Việc đó chấm dứt quyền truy cập của họ trong client của bạn, cùng mọi token thuộc về nó. Endpoint trả về 200 ngay cả với token nó không biết, nên không thể dùng nó để dò xem token nào tồn tại.
Mọi người cũng có thể tự xem các ứng dụng họ đã kết nối và thu hồi quyền truy cập (GET /user/oauth-grants, DELETE /user/oauth-grants/{id}). Từ lúc đó client của bạn không thể lấy token mới, nên hãy coi việc làm mới bị từ chối là đã đăng xuất.
curl https://api.heyatli.com/oauth2/revoke \
-u 'CLIENT_ID:CLIENT_SECRET' \
-d token=THE_REFRESH_TOKEN \
-d client_id=CLIENT_IDLỗi
Các endpoint token, thu hồi và đăng ký trả lời bằng định dạng lỗi riêng của OAuth, {"error": "invalid_grant", "error_description": "…"}, thay vì định dạng thông thường của API. Hãy rẽ nhánh theo error, đừng bao giờ theo phần mô tả. Mọi phản hồi từ endpoint token đều có Cache-Control: no-store.
invalid_grant: mã đã được dùng hoặc đã hết hạn, verifier sai, hoặc refresh token được dùng lại sau thời gian ân hạn. Hãy đăng nhập lại cho người đó.invalid_target:resourcekhông phải địa chỉ mà token có thể dành cho, hoặc chỉ đến một mã giới thiệu không tồn tại.invalid_client: thông tin xác thực của client sai hoặc bị thiếu. Mọi loại đều thất bại theo cùng một cách.invalid_scope: yêu cầu làm mới đòi nhiều hơn những gì người dùng đã cho phép.