OAuth

OAuth

Deixe as pessoas entrarem no seu cliente com a conta Atli delas, e aja em nome delas, com OAuth 2.1 padrão: descoberta, PKCE, refresh tokens que se renovam a cada uso e revogação. Qualquer cliente pode usar: um app, um bot, um site, um servidor ou um assistente de IA.

Quando usar

  • Assistentes de IA são um desses clientes. Ao se conectar ao servidor MCP da Atli, eles fazem o login das pessoas desse jeito por conta própria, então não há nada a construir para eles. O mesmo fluxo serve a todo cliente.

  • Qualquer cliente que age por uma pessoa: um app de celular, um site, um bot ou um servidor que mostra a alguém os eSIMs dela ou compra um para ela, com a própria conta Atli. Registre um cliente seu, e os tokens dele funcionam na API da Atli.

  • Quem entra por um cliente que é seu, ou pelo seu endereço de parceiro do servidor MCP, pode ser vinculado a você como sua indicação. Isso vem desativado por padrão, e a Atli pode ativar para a sua conta. De qualquer forma, seu cliente pode ele mesmo dar a você o crédito pelas pessoas que traz: veja “Receba o crédito pelas pessoas que você traz”, mais abaixo.

  • Só pode se conectar uma conta na qual a pessoa consiga entrar de novo em outro lugar: uma com email, inclusive via Google, Apple ou Facebook, ou com Telegram ou WhatsApp.

Como funciona

A API da Atli é o servidor de autorização. O login e a tela de consentimento ficam em heyatli.com, em /connect, então seu app nunca vê uma senha. Leia cada endpoint dos metadados em vez de escrevê-los no código. Os endereços aqui acompanham o seletor de Sandbox e Produção.

Registre um cliente uma vez. Depois, para cada pessoa: envie-a ao endpoint de autorização com um challenge PKCE, pegue o código com que ela volta, troque-o por tokens e chame a API com o access token. Renove-o quando expirar e revogue-o quando a pessoa sair.

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 que serve um token

Toda solicitação informa o endereço para o qual o token se destina, como resource (RFC 8707), e o token só funciona ali. Um endereço que não seja um destes, ou um código de indicação que não existe, é recusado com invalid_target, então nenhum dos dois pode ser adivinhado.

EndereçoQuem pode pedirO que o token faz
A API da Atli
https://api.heyatli.com
Só um cliente que é seu

Um bearer token para a API, que age como a pessoa, igual a um de /login.

O servidor MCP da Atli
https://mcp.heyatli.com
Qualquer cliente

Permite que um assistente aja em nome da pessoa pelas ferramentas MCP.

Seu endereço de parceiro
https://mcp.heyatli.com/r/YOUR_CODE
Qualquer cliente

O mesmo, e a pessoa pode ser vinculada a você como sua indicação, depois que isso for ativado para a sua conta.

Registrar um cliente

Há três formas. Para o seu próprio app, registre um cliente seu: é o único tipo que pode pedir acesso à API da Atli.

  1. Um cliente seu

    Com login na sua conta, POST /user/oauth-clients. Você pode ter 10. Escolha public para tudo que roda no dispositivo de outra pessoa, como um app de celular ou uma página no navegador dela, e confidential para um servidor que consegue guardar um segredo.

    O client_secret de um cliente confidencial está nesta resposta e em nenhum outro lugar. Se você perdê-lo, substitua-o com POST /user/oauth-clients/{id}/rotate-secret. Sua conta precisa de um email, porque um cliente precisa de um código de indicação; um é criado para você se ainda não tiver.

    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",
      …
    }
  2. Registro dinâmico

    POST /oauth2/register (RFC 7591), sem login. É assim que a maioria dos assistentes se registra. Permite 10 registros por hora a partir de um mesmo endereço, e um cliente ao qual ninguém nunca se conecta é excluído após 30 dias.

    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",
      …
    }
  3. Um documento de metadados de client ID

    Use como client_id um endereço https hospedado por você, que sirva um documento JSON descrevendo o cliente. A Atli o lê quando alguém entra, então não há nada a registrar. A especificação do MCP prefere isso ao 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"
    }
  • As URIs de redirecionamento são comparadas exatamente. Cada uma é https, ou http em localhost, 127.0.0.1 ou [::1], onde só a porta pode variar, sem fragmento. Um cliente pode ter até 10.

Fazer o login de uma pessoa

O fluxo de código de autorização com PKCE (S256), que todo cliente usa, público ou confidencial.

  1. Gere um par PKCE

    Guarde o verifier para a troca do código e envie o challenge SHA-256 dele com a solicitação. Gere um par novo, e um state novo, a cada login.

    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)));
  2. Mande a pessoa entrar

    Abra o endpoint de autorização no navegador dela. scope é api, e offline_access acrescenta um refresh token. A pessoa entra e aceita em heyatli.com/connect, que mostra o nome do seu cliente, para onde ele a manda de volta e se ela viraria indicação de alguém. A solicitação vale por 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);
  3. Pegue o código que ela traz

    Ela volta à sua URI de redirecionamento com code, o seu state e iss. Confira se state é seu e se iss é o emissor. Se ela recusou, vem error=access_denied no 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.com
  4. Troque o código por tokens

    Envie o código com o verifier, o mesmo redirect_uri e o mesmo resource, e as credenciais do seu cliente, se ele tiver. Um código vale por 60 segundos e funciona uma vez: enviá-lo duas vezes, ou com o verifier errado, encerra o acesso da pessoa, pois outra pessoa pode estar com ele.

    O access token vale por expires_in segundos, 15 minutos. Um refresh token sempre vem junto.

    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"
    }

Manter a pessoa conectada

Quando o access token expirar, peça um novo com o refresh token. Você pode pedir um scope mais restrito, nunca um mais amplo.

O refresh token muda a cada uso, então guarde o novo toda vez. Se uma resposta nunca chegou até você, enviar o token antigo de novo por um curto período devolve a mesma resposta. Depois disso, o token antigo indica que ele foi copiado, e o acesso da pessoa 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.com

Usar o token

Envie-o como Authorization: Bearer ACCESS_TOKEN, para o endereço para o qual foi emitido e para nenhum outro.

Um token para a API da Atli age como a pessoa em todas as rotas da API, como um token de /login. Trate-o como opaco: o conteúdo dele pode mudar.

Um token para um servidor MCP é um JWT assinado com EdDSA. O servidor o verifica com as chaves em /oauth2/jwks, pelo kid dele, e confere se o aud é o seu próprio endereço. Ele não traz email nem outros dados: só sub (o id da pessoa), scope, grant_id e os horários.

curl https://api.heyatli.com/esims \
  -H 'Authorization: Bearer ACCESS_TOKEN'

Receba o crédito pelas pessoas que você traz

Vincular a você as pessoas que entram pelo seu cliente, ou pelo seu endereço de parceiro, vem desativado por padrão, e a Atli pode ativar isso para a sua conta. Esteja ativado ou não, seu cliente pode ele mesmo vincular uma pessoa conectada ao seu código de indicação, com o access token dela: POST /user/referral/redeem com o seu code.

result diz o que aconteceu: ok vinculou a pessoa, already significa que ela já tem quem a indicou, notnew que ela já comprou antes, own que é o seu próprio código e notfound que o código não existe. Só pode ser vinculada uma pessoa sem indicação que ainda não comprou, e só uma 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
}

Sair e remover o acesso

Quando alguém sair do seu app, revogue o refresh token dessa pessoa. Isso encerra o acesso dela no seu cliente, com todos os tokens dele. A resposta é 200 mesmo para um token desconhecido, para que não dê para usá-lo para descobrir quais tokens existem.

As pessoas também podem ver os apps que conectaram e remover o acesso por conta própria (GET /user/oauth-grants, DELETE /user/oauth-grants/{id}). A partir daí seu cliente não consegue um token novo, então trate uma renovação recusada como logout.

curl https://api.heyatli.com/oauth2/revoke \
  -u 'CLIENT_ID:CLIENT_SECRET' \
  -d token=THE_REFRESH_TOKEN \
  -d client_id=CLIENT_ID

Erros

Os endpoints de token, revogação e registro respondem com o corpo de erro próprio do OAuth, {"error": "invalid_grant", "error_description": "…"}, em vez do habitual da API. Decida com base em error, nunca na descrição. Toda resposta do endpoint de token traz Cache-Control: no-store.

  • invalid_grant: o código já foi usado ou expirou, o verifier está errado, ou um refresh token foi usado de novo depois do período de tolerância. Faça o login da pessoa novamente.

  • invalid_target: resource não é um endereço ao qual um token pode se destinar, ou indica um código de indicação que não existe.

  • invalid_client: as credenciais do cliente estão erradas ou faltando. Todos os tipos falham do mesmo jeito.

  • invalid_scope: uma renovação pediu mais do que a pessoa permitiu.