OAuth
Permettez aux utilisateurs de se connecter à votre client avec leur compte Atli, et agissez en leur nom, avec OAuth 2.1 standard : découverte, PKCE, jetons d’actualisation renouvelés à chaque usage et révocation. Tout client peut l’utiliser : une application, un bot, un site web, un serveur ou un assistant IA.
Quand l’utiliser
Les assistants IA sont un client de ce type. En se connectant au serveur MCP d’Atli, ils connectent les utilisateurs de cette façon d’eux-mêmes : vous n’avez donc rien à construire pour eux. Le même flux sert à tous les clients.
Tout client qui agit pour une personne : une application mobile, un site web, un bot ou un serveur qui montre à quelqu’un ses eSIM ou lui en achète une, avec son propre compte Atli. Enregistrez votre propre client, et ses jetons fonctionnent sur l’API d’Atli.
Les personnes qui se connectent via un client qui vous appartient, ou à votre adresse partenaire du serveur MCP, peuvent vous être rattachées comme filleuls. C’est désactivé par défaut, et Atli peut l’activer pour votre compte. Dans tous les cas, votre client peut lui-même faire valoir les personnes qu’il amène : voir « Faire valoir les personnes que vous amenez », plus bas.
Seul un compte auquel on peut se reconnecter ailleurs peut être connecté : un compte avec une adresse e-mail, y compris via Google, Apple ou Facebook, ou avec Telegram ou WhatsApp.
Fonctionnement
L’API d’Atli est le serveur d’autorisation. La connexion et l’écran de consentement se trouvent sur heyatli.com, à /connect, si bien que votre application ne voit jamais de mot de passe. Lisez chaque endpoint dans les métadonnées plutôt que de les écrire en dur. Les adresses de cette page suivent le sélecteur Sandbox / Production.
Enregistrez un client une fois. Ensuite, pour chaque personne : envoyez-la vers l’endpoint d’autorisation avec un challenge PKCE, récupérez le code avec lequel elle revient, échangez-le contre des jetons, et appelez l’API avec le jeton d’accès. Actualisez-le quand il expire, et révoquez-le quand elle se déconnecte.
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,
…
}À quoi sert un jeton
Chaque requête indique l’adresse à laquelle son jeton est destiné, dans resource (RFC 8707), et le jeton ne fonctionne que là. Une adresse qui ne figure pas ci-dessous, ou un code de parrainage qui n’existe pas, est refusé avec invalid_target : ni l’un ni l’autre ne peut donc être deviné.
| Adresse | Qui peut le demander | Ce que fait le jeton |
|---|---|---|
L’API d’Atli https://api.heyatli.com | Uniquement un client qui vous appartient | Un jeton bearer pour l’API, qui agit en tant que la personne, comme celui de |
Le serveur MCP d’Atli https://mcp.heyatli.com | Tout client | Permet à un assistant d’agir au nom de la personne via les outils MCP. |
Votre adresse partenaire https://mcp.heyatli.com/r/YOUR_CODE | Tout client | La même chose, et la personne peut vous être rattachée comme filleul, une fois cette option activée pour votre compte. |
Enregistrer un client
Il y a trois façons de faire. Pour votre propre application, enregistrez votre propre client : c’est le seul type qui peut demander l’accès à l’API d’Atli.
Votre propre client
Connecté avec votre compte,
POST /user/oauth-clients. Vous pouvez en avoir 10. Choisissezpublicpour tout ce qui tourne sur l’appareil de quelqu’un d’autre, comme une application mobile ou une page dans son navigateur, etconfidentialpour un serveur capable de garder un secret.Le
client_secretd’un client confidentiel figure dans cette réponse et nulle part ailleurs. Si vous le perdez, remplacez-le avecPOST /user/oauth-clients/{id}/rotate-secret. Votre compte doit avoir une adresse e-mail, car un client a besoin d’un code de parrainage ; un code est créé pour vous si vous n’en avez pas encore.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", … }Enregistrement dynamique
POST /oauth2/register(RFC 7591), sans connexion. C’est ainsi que s’enregistrent la plupart des assistants. Il autorise 10 enregistrements par heure depuis une même adresse, et un client auquel personne ne se connecte jamais est supprimé au bout de 30 jours.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", … }Un document de métadonnées d’identifiant client
Utilisez comme
client_idune adressehttpsque vous hébergez, qui sert un document JSON décrivant le client. Atli le lit quand quelqu’un se connecte, il n’y a donc rien à enregistrer. La spécification MCP préfère cette méthode à l’enregistrement dynamique.// 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" }
Les URI de redirection doivent correspondre exactement. Chacune est en
https, ou enhttpsurlocalhost,127.0.0.1ou[::1], où seul le port peut varier, sans fragment. Un client peut en avoir jusqu’à 10.
Connecter une personne
Le flux authorization code avec PKCE (S256), utilisé par tous les clients, publics comme confidentiels.
Créer une paire PKCE
Conservez le verifier pour l’échange du code, et envoyez son challenge SHA-256 avec la requête. Créez une nouvelle paire, et un nouveau
state, pour chaque connexion.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)));L’envoyer se connecter
Ouvrez l’endpoint d’autorisation dans son navigateur.
scopevautapi, etoffline_accessajoute un jeton d’actualisation. La personne se connecte et accepte sur heyatli.com/connect, qui affiche le nom de votre client, l’adresse où il la renvoie et si elle deviendrait le filleul de quelqu’un. La requête est valable 10 minutes.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);Récupérer le code qu’elle rapporte
Elle revient sur votre URI de redirection avec
code, votrestateetiss. Vérifiez questateest bien le vôtre et queissest l’émetteur. Si elle a refusé, la réponse contienterror=access_deniedà la place.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Échanger le code contre des jetons
Envoyez le code avec le verifier, la même
redirect_uriet la mêmeresource, ainsi que les identifiants de votre client s’il en a. Un code est valable 60 secondes et ne sert qu’une fois : l’envoyer deux fois, ou avec le mauvais verifier, met fin à l’accès de la personne, car quelqu’un d’autre pourrait l’avoir.Le jeton d’accès est valable
expires_insecondes, soit 15 minutes. Un jeton d’actualisation l’accompagne toujours.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" }
Garder la personne connectée
Quand le jeton d’accès expire, demandez-en un nouveau avec le jeton d’actualisation. Vous pouvez demander un scope plus restreint, jamais un plus large.
Le jeton d’actualisation change à chaque utilisation : conservez le nouveau à chaque fois. Si une réponse ne vous est jamais parvenue, renvoyer l’ancien jeton pendant un court moment renvoie la même réponse. Passé ce délai, l’ancien jeton signifie qu’il a été copié, et l’accès de la personne prend fin.
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.comUtiliser le jeton
Envoyez-le sous la forme Authorization: Bearer ACCESS_TOKEN, à l’adresse pour laquelle il a été émis et nulle part ailleurs.
Un jeton pour l’API d’Atli agit en tant que la personne sur chaque route de l’API, comme un jeton obtenu via /login. Traitez-le comme opaque : son contenu peut changer.
Un jeton pour un serveur MCP est un JWT signé avec EdDSA. Le serveur le vérifie avec les clés de /oauth2/jwks, d’après son kid, et vérifie que son aud est sa propre adresse. Il ne contient ni e-mail ni autre information : seulement sub (l’identifiant de la personne), scope, grant_id et les horodatages.
curl https://api.heyatli.com/esims \
-H 'Authorization: Bearer ACCESS_TOKEN'Faire valoir les personnes que vous amenez
Le rattachement à vous des personnes qui se connectent via votre client, ou à votre adresse partenaire, est désactivé par défaut, et Atli peut l’activer pour votre compte. Qu’il soit activé ou non, votre client peut lui-même rattacher une personne connectée à votre code de parrainage, avec son jeton d’accès : POST /user/referral/redeem avec votre code.
result indique ce qui s’est passé : ok l’a rattachée, already signifie qu’elle a déjà un parrain, notnew qu’elle a déjà acheté, own qu’il s’agit de votre propre code, et notfound que le code n’existe pas. Seule une personne sans parrain qui n’a encore rien acheté peut être rattachée, et une seule fois.
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
}Déconnecter et retirer l’accès
Quand quelqu’un se déconnecte de votre application, révoquez son jeton d’actualisation. Cela met fin à son accès dans votre client, avec tous les jetons associés. La réponse est 200 même pour un jeton inconnu, afin qu’on ne puisse pas s’en servir pour savoir quels jetons existent.
Les utilisateurs peuvent aussi voir les applications qu’ils ont connectées et leur retirer l’accès eux-mêmes (GET /user/oauth-grants, DELETE /user/oauth-grants/{id}). À partir de là, votre client ne peut plus obtenir de nouveau jeton : traitez une actualisation refusée comme une déconnexion.
curl https://api.heyatli.com/oauth2/revoke \
-u 'CLIENT_ID:CLIENT_SECRET' \
-d token=THE_REFRESH_TOKEN \
-d client_id=CLIENT_IDErreurs
Les endpoints de jeton, de révocation et d’enregistrement répondent avec le format d’erreur propre à OAuth, {"error": "invalid_grant", "error_description": "…"}, plutôt qu’avec celui habituel de l’API. Basez votre logique sur error, jamais sur la description. Chaque réponse de l’endpoint de jeton contient Cache-Control: no-store.
invalid_grant: le code a déjà servi ou a expiré, le verifier est incorrect, ou un jeton d’actualisation a été réutilisé après son délai de grâce. Reconnectez la personne.invalid_target:resourcen’est pas une adresse à laquelle un jeton peut être destiné, ou désigne un code de parrainage qui n’existe pas.invalid_client: les identifiants du client sont incorrects ou manquants. Tous les types échouent de la même façon.invalid_scope: une actualisation a demandé plus que ce que la personne a autorisé.