OAuth
Deja que la gente inicie sesión en tu cliente con su cuenta de Atli, y actúa en su nombre, con OAuth 2.1 estándar: descubrimiento, PKCE, tokens de actualización que rotan y revocación. Cualquier cliente puede usarlo: una app, un bot, un sitio web, un servidor o un asistente de IA.
Cuándo usarlo
Los asistentes de IA son uno de esos clientes. Al conectarse al servidor MCP de Atli, inician la sesión de la gente de esta forma por su cuenta, así que no hay que construir nada para ellos. El mismo flujo sirve para todos los clientes.
Cualquier cliente que actúe en nombre de una persona: una app de móvil, un sitio web, un bot o un servidor que le muestre a alguien sus eSIM o le compre una, con su propia cuenta de Atli. Registra un cliente propio y sus tokens funcionarán en la API de Atli.
Quienes inician sesión a través de un cliente tuyo, o en tu dirección de partner del servidor MCP, pueden quedar vinculados a ti como tus referidos. Eso está desactivado por defecto, y Atli puede activarlo para tu cuenta. En cualquier caso, tu cliente puede atribuirte él mismo la gente que trae: consulta «Que se te atribuya la gente que traes», más abajo.
Solo puede conectarse una cuenta con la que se pueda volver a iniciar sesión en otro lugar: una con email, también a través de Google, Apple o Facebook, o con Telegram o WhatsApp.
Cómo funciona
La API de Atli es el servidor de autorización. El inicio de sesión y la pantalla de consentimiento están en heyatli.com, en /connect, así que tu app nunca ve una contraseña. Lee cada endpoint de los metadatos en lugar de escribirlos a mano. Las direcciones de esta página siguen el selector de Sandbox y Producción.
Registra un cliente una vez. Después, para cada persona: envíala al endpoint de autorización con un challenge de PKCE, toma el código con el que vuelve, cámbialo por tokens y llama a la API con el token de acceso. Renuévalo cuando caduque y revócalo cuando cierre sesión.
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,
…
}Para qué sirve un token
Cada solicitud indica la dirección para la que es su token, como resource (RFC 8707), y el token solo funciona ahí. Una dirección que no sea una de estas, o un código de referido que no exista, se rechaza con invalid_target, así que no se puede adivinar ninguno de los dos.
| Dirección | Quién puede pedirlo | Qué hace el token |
|---|---|---|
La API de Atli https://api.heyatli.com | Solo un cliente tuyo | Un token bearer para la API, que actúa como la persona, igual que uno de |
El servidor MCP de Atli https://mcp.heyatli.com | Cualquier cliente | Permite que un asistente actúe en nombre de la persona a través de las herramientas MCP. |
Tu dirección de partner https://mcp.heyatli.com/r/YOUR_CODE | Cualquier cliente | Lo mismo, y la persona puede quedar vinculada a ti como tu referido, una vez que eso esté activado para tu cuenta. |
Registrar un cliente
Hay tres formas. Para tu propia app, registra un cliente propio: es el único tipo que puede pedir acceso a la API de Atli.
Un cliente propio
Con tu sesión iniciada,
POST /user/oauth-clients. Puedes tener 10. Eligepublicpara cualquier cosa que se ejecute en el dispositivo de otra persona, como una app de móvil o una página en su navegador, yconfidentialpara un servidor que pueda guardar un secreto.El
client_secretde un cliente confidencial está en esta respuesta y en ningún otro sitio. Si lo pierdes, sustitúyelo conPOST /user/oauth-clients/{id}/rotate-secret. Tu cuenta necesita un email, porque un cliente necesita un código de referido; si aún no tienes uno, se crea para ti.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", … }Registro dinámico
POST /oauth2/register(RFC 7591), sin iniciar sesión. Es como se registran la mayoría de los asistentes. Permite 10 registros por hora desde una misma dirección, y un cliente al que nadie se conecta nunca se elimina a los 30 días.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 documento de metadatos de ID de cliente
Usa como
client_iduna direcciónhttpsalojada por ti que sirva un documento JSON que describa el cliente. Atli lo lee cuando alguien inicia sesión, así que no hay nada que registrar. La especificación de MCP prefiere esto al registro dinámico.// 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" }
Las URI de redirección se comparan de forma exacta. Cada una es
https, ohttpenlocalhost,127.0.0.1o[::1], donde solo puede cambiar el puerto, sin fragmento. Un cliente puede tener hasta 10.
Iniciar la sesión de una persona
El flujo de código de autorización con PKCE (S256), que usan todos los clientes, públicos o confidenciales.
Crea un par PKCE
Guarda el verifier para el intercambio del código y envía su challenge SHA-256 con la solicitud. Crea un par nuevo, y un
statenuevo, para cada inicio de sesión.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)));Envíala a iniciar sesión
Abre el endpoint de autorización en su navegador.
scopeesapi, yoffline_accessañade un token de actualización. La persona inicia sesión y acepta en heyatli.com/connect, que muestra el nombre de tu cliente, adónde la devuelve y si pasaría a ser referido de alguien. La solicitud dura 10 minutos.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);Recoge el código que trae
Vuelve a tu URI de redirección con
code, tustateeiss. Comprueba questatees tuyo y queisses el emisor. Si dijo que no, traeerror=access_denieden su lugar.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.comCambia el código por tokens
Envía el código con el verifier, la misma
redirect_uriy el mismoresource, y las credenciales de tu cliente si las tiene. Un código dura 60 segundos y sirve una sola vez: enviarlo dos veces, o con el verifier equivocado, pone fin al acceso de la persona, porque podría tenerlo otra persona.El token de acceso dura
expires_insegundos, 15 minutos. Siempre viene acompañado de un token de actualización.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" }
Mantener la sesión iniciada
Cuando caduque el token de acceso, pide uno nuevo con el token de actualización. Puedes pedir un scope más limitado, nunca uno más amplio.
El token de actualización cambia en cada uso, así que guarda el nuevo cada vez. Si una respuesta nunca te llegó, volver a enviar el token anterior durante un breve periodo devuelve la misma respuesta. Después, el token anterior indica que se ha copiado, y el acceso de la persona termina.
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.comUsar el token
Envíalo como Authorization: Bearer ACCESS_TOKEN, a la dirección para la que se emitió y a ninguna otra.
Un token para la API de Atli actúa como la persona en todas las rutas de la API, igual que un token de /login. Trátalo como opaco: lo que contiene puede cambiar.
Un token para un servidor MCP es un JWT firmado con EdDSA. El servidor lo verifica con las claves de /oauth2/jwks, según su kid, y comprueba que su aud sea su propia dirección. No contiene email ni otros datos: solo sub (el id de la persona), scope, grant_id y las fechas.
curl https://api.heyatli.com/esims \
-H 'Authorization: Bearer ACCESS_TOKEN'Que se te atribuya la gente que traes
Vincular a ti a las personas que inician sesión a través de tu cliente, o en tu dirección de partner, está desactivado por defecto, y Atli puede activarlo para tu cuenta. Esté activado o no, tu cliente puede vincular él mismo a una persona con la sesión iniciada a tu código de referido, con su token de acceso: POST /user/referral/redeem con tu code.
result indica qué ha pasado: ok la ha vinculado, already significa que ya tiene un referente, notnew que ya ha comprado antes, own que es tu propio código y notfound que el código no existe. Solo se puede vincular a una persona sin referente que aún no haya comprado, y solo una vez.
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
}Cerrar sesión y retirar el acceso
Cuando alguien cierre sesión en tu app, revoca su token de actualización. Eso termina su acceso en tu cliente, con todos los tokens asociados. Responde 200 incluso para un token que no conoce, para que no pueda usarse para averiguar qué tokens existen.
La gente también puede ver las apps que ha conectado y retirarles el acceso por su cuenta (GET /user/oauth-grants, DELETE /user/oauth-grants/{id}). A partir de ahí tu cliente no puede obtener un token nuevo, así que trata una renovación rechazada como un cierre de sesión.
curl https://api.heyatli.com/oauth2/revoke \
-u 'CLIENT_ID:CLIENT_SECRET' \
-d token=THE_REFRESH_TOKEN \
-d client_id=CLIENT_IDErrores
Los endpoints de token, revocación y registro responden con el cuerpo de error propio de OAuth, {"error": "invalid_grant", "error_description": "…"}, en lugar del habitual de la API. Decide según error, nunca según la descripción. Todas las respuestas del endpoint de token llevan Cache-Control: no-store.
invalid_grant: el código ya se usó o caducó, el verifier es incorrecto, o se volvió a usar un token de actualización después de su periodo de gracia. Vuelve a iniciar la sesión de la persona.invalid_target:resourceno es una dirección para la que pueda ser un token, o indica un código de referido que no existe.invalid_client: las credenciales del cliente son incorrectas o faltan. Todos los tipos fallan igual.invalid_scope: una renovación pidió más de lo que la persona permitió.