# Conexão automática com o Monde: guia para agentes de código Este arquivo é para o agente de código (Claude Code, Cursor ou outro) que vai implementar a integração no projeto do parceiro. Siga os passos em ordem. Não pule a verificação do passo 9. Valores entre `{{ }}` são do parceiro. Quando o arquivo vier com eles preenchidos, use os valores como estão. Quando vierem em branco, peça ao usuário. - `{{CLIENT_ID}}`: identificador público do parceiro. - `{{REDIRECT_URI}}`: endereço de retorno cadastrado no Monde. - `MONDE_CLIENT_SECRET`: variável de ambiente com o secret. O valor nunca aparece neste arquivo. Host de todos os endpoints: `https://web.monde.com.br`. ## 1. O que será implementado Um botão "Conectar com o Monde" no site do parceiro. O botão leva o usuário da agência de viagens ao Monde, que pede login e consentimento. Depois, o Monde devolve um `code` para o backend do parceiro. O backend troca o `code` por um token e passa a chamar a API v3 do Monde em nome daquela agência. O fluxo é OAuth 2.0 Authorization Code com PKCE (`S256`) obrigatório. ## 2. Antes de começar: passos que só o humano faz Pare e peça ao usuário estes itens. Não continue sem eles. 1. **Homologação.** O parceiro pede a homologação ao comercial ou ao suporte do Monde. Ele informa o nome, o e-mail que recebe as credenciais e a `{{REDIRECT_URI}}`. A `{{REDIRECT_URI}}` precisa usar `https`. `http`, `localhost` e `127.0.0.1` não são aceitos. 2. **Credenciais.** O Monde envia por e-mail um link que vale 1 hora. A página do link mostra o `client_id` e o `client_secret`. O secret aparece uma única vez. 3. **Secret na variável de ambiente.** Peça ao usuário para gravar o secret em `MONDE_CLIENT_SECRET` no ambiente do backend (arquivo `.env` fora do controle de versão, ou o gerenciador de segredos do projeto). **Não peça o secret no chat.** Se o usuário colar o secret no chat, não o escreva em nenhum arquivo. Peça a ele para gravar na variável de ambiente e para pedir ao Monde um secret novo. 4. **Ativação.** Peça ao usuário para rodar este comando uma vez, no terminal dele: ```bash curl -X POST https://web.monde.com.br/oauth/homologation -u "{{CLIENT_ID}}:$MONDE_CLIENT_SECRET" ``` - `200` com `{"status":"active"}`: a conexão está ativa. Continue. - `401` com corpo vazio: `client_id` ou secret errado, ou parceiro desativado. Pare e peça ao usuário para falar com o suporte do Monde. ## 3. Regras invioláveis - Faça a troca do `code` pelo token **só no backend**. - O `client_secret` e o token **nunca** vão para o navegador, para o app do usuário, para log ou para o controle de versão. - Gere um `state` e um `code_verifier` **novos a cada tentativa** de conexão. Guarde os dois na sessão do servidor do usuário. Não reaproveite valores. - Use a `redirect_uri` **idêntica** à cadastrada, caractere por caractere, na autorização e na troca. Uma barra no fim já é diferença. - Não envie `scope`. O que o parceiro pode fazer vem da homologação. - Não decodifique nem altere o `code` nem o token. Os dois são opacos. ## 4. Identifique a stack antes de codar Leia o projeto antes de escrever código e responda: 1. Qual é a linguagem e o framework do backend? Onde ficam as rotas? 2. Como o projeto guarda sessão do usuário no servidor? 3. Como o projeto lê variáveis de ambiente? 4. Onde e como o projeto grava dados sensíveis por cliente, como tokens de outras integrações? 5. Qual cliente HTTP o backend já usa? Use o que o projeto já tem. Não adicione biblioteca de OAuth só para este fluxo: são duas requisições HTTP. ## 5. Botão Texto: **Conectar com o Monde**. O logo é hospedado pelo Monde: - `https://web.monde.com.br/logo-monde-conectar.svg`: colorido, para fundo claro. - `https://web.monde.com.br/logo-monde-conectar-branco.svg`: branco, para fundo azul. Estilo padrão (fundo branco): ```html Monde Conectar com o Monde ``` Variação com fundo azul: `background:#2c7be5;color:#fff;border:1px solid #2c7be5` e o logo branco. O `href` aponta para uma rota do **seu backend** (no exemplo, `/monde/conectar`). Essa rota monta a URL de autorização a cada clique. Nunca use um link estático para o Monde. Estados da tela: | Estado | Quando | O que mostrar | |---|---|---| | Desconectado | Não há token guardado para a agência | O botão "Conectar com o Monde" | | Conectado | Há token guardado para a agência | "Conectado ao Monde" e um botão "Desconectar" | | Erro | O retorno trouxe `error`, o `state` não bateu ou a troca do token falhou | Uma mensagem curta com o motivo e o botão "Conectar com o Monde" de novo | Quando a API responder `401`, apague o token: a tela volta ao estado desconectado (passo 8). ## 6. Redirecionamento para a autorização Na rota do botão (`/monde/conectar`): 1. Gere o `state`: 32 bytes aleatórios em base64url. 2. Gere o `code_verifier`: 48 bytes aleatórios em base64url, que dão 64 caracteres. O tamanho aceito vai de 43 a 128. 3. Calcule o `code_challenge = BASE64URL(SHA256(code_verifier))`, sem `=` no fim. 4. Grave na sessão do servidor o `state`, o `code_verifier` e o identificador da agência no seu sistema (a conta do usuário logado que clicou no botão). A resposta do token não identifica a agência: é esse identificador que liga o token a ela no passo 8. 5. Redirecione o navegador (HTTP 302) para: ``` https://web.monde.com.br/oauth/authorize?response_type=code&client_id={{CLIENT_ID}}&redirect_uri={{REDIRECT_URI}}&state=STATE&code_challenge=CODE_CHALLENGE&code_challenge_method=S256 ``` Codifique cada valor para URL. Exemplo em Node.js: ```js const crypto = require("crypto"); const state = crypto.randomBytes(32).toString("base64url"); const codeVerifier = crypto.randomBytes(48).toString("base64url"); const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url"); ``` No Monde, a agência faz login, escolhe a empresa e consente. Só um administrador da agência pode autorizar. O Monde **não volta** para a `{{REDIRECT_URI}}` e mostra um erro `400` na própria página quando: - o `client_id` não é de um parceiro ativo e homologado; - a `redirect_uri` não é a cadastrada; - falta o `code_challenge`, ou o `code_challenge_method` não é `S256`. ## 7. Callback: valide o state e troque o código O Monde redireciona o navegador para a `{{REDIRECT_URI}}`. Aprovado: ``` {{REDIRECT_URI}}?code=CODE&state=STATE ``` Recusado: ``` {{REDIRECT_URI}}?error=access_denied&error_description=...&state=STATE ``` Na rota da `{{REDIRECT_URI}}`: 1. Leia o `state` e o `code_verifier` da sessão para variáveis locais e apague os dois da sessão. 2. Se o `state` da URL for diferente do `state` lido no item 1, ou faltar, pare. Mostre o estado de erro e não chame o Monde. 3. Se vier `error`, mostre o estado de erro. `access_denied` quer dizer que a agência recusou. 4. Troque o `code` pelo token, no backend, em até 10 minutos. O `code` só vale uma vez. Use o valor do parâmetro `code` do jeito que o framework entrega (já decodificado da URL), sem nenhuma outra transformação. ```bash curl -X POST https://web.monde.com.br/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=authorization_code" \ --data-urlencode "code=CODE" \ --data-urlencode "redirect_uri={{REDIRECT_URI}}" \ --data-urlencode "client_id={{CLIENT_ID}}" \ --data-urlencode "client_secret=$MONDE_CLIENT_SECRET" \ --data-urlencode "code_verifier=CODE_VERIFIER" ``` Resposta `200`: ```json { "access_token": "YWdlbmNpYXw2ZjFj...", "token_type": "Bearer", "scope": "full_access", "created_at": 1790602784 } ``` A resposta não traz `expires_in` nem `refresh_token`. Isso é esperado. Nada volta para a `{{REDIRECT_URI}}` quando quem entrou não é administrador da agência, ou quando a empresa não tem a licença exigida. O usuário vê a explicação numa página do Monde. Não espere o callback nesses casos. ## 8. Armazenamento, uso e revogação **Armazenamento** - Guarde o `access_token` no banco do backend, cifrado, ligado ao identificador da agência que você gravou na sessão no passo 6. É um token por agência conectada. - O token não expira e não tem refresh. Trate-o como senha. **Uso** ``` Authorization: Bearer ACCESS_TOKEN Content-Type: application/json ``` - Toda chamada à API v3 (`https://web.monde.com.br/api/v3/...`) leva esses cabeçalhos. - Criar venda (`POST /api/v3/sales`) também exige o cabeçalho `Idempotency-Key`, com um UUID v4 novo por venda. O corpo leva `company_identifier` com o CNPJ da empresa da agência. - A documentação completa da venda está em `https://web.monde.com.br/api/v3/documentation`. **Quando a API responde `401`** - A agência excluiu a conexão, ou o Monde desativou o parceiro. - Apague o token e mostre o estado desconectado. - Não repita a chamada. **Revogação.** Quando o usuário clicar em "Desconectar", revogue o token no backend e depois apague-o: ```bash curl -X POST https://web.monde.com.br/oauth/revoke \ --data-urlencode "token=ACCESS_TOKEN" \ --data-urlencode "client_id={{CLIENT_ID}}" \ --data-urlencode "client_secret=$MONDE_CLIENT_SECRET" ``` - `200` com `{}`: a conexão com a agência foi excluída. - `403` com `{"error":"unauthorized_client",...}`: `client_id` ou secret errado, ou o token não é deste `client_id`. Apague o token do seu banco mesmo quando a revogação falhar, e registre o erro no log sem o token. Se ficar com o token, o usuário não consegue sair do estado conectado. ## 9. Verificação: quando a integração está pronta Não há ambiente de testes separado: use uma agência de verdade, com um administrador que autorize. Depois da conexão, chame com o token guardado: ```bash curl https://web.monde.com.br/api/v3/sales -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" ``` | Resposta | Significado | |---|---| | `403` com `{"errors":["Você não tem permissão para executar essa ação."]}` | O token autentica. **A integração está pronta.** O parceiro cria vendas, mas não as lê. | | `401` com `{"errors":["Credenciais de acesso não são válidas."]}` | Token errado ou conexão excluída. Refaça a partir do passo 6. | A integração só está pronta depois de três coisas: o fluxo completo pelo botão, a troca do token com `200` e esta chamada respondendo `403`. Não use `POST /api/v3/sales` para testar: a venda é criada de verdade na agência. ## 10. Erros comuns | Onde | Resposta | Causa | O que fazer | |---|---|---|---| | Ativação | `401` vazio | Credencial errada ou parceiro desativado | Conferir o `client_id` e o `MONDE_CLIENT_SECRET`. Se persistir, falar com o suporte do Monde | | Autorização | Página do Monde com `400` | Parceiro inativo, `redirect_uri` diferente da cadastrada, ou PKCE ausente ou diferente de `S256` | Conferir a `redirect_uri` caractere por caractere e o envio de `code_challenge` com `code_challenge_method=S256` | | Autorização | Página do Monde sem voltar ao callback | Usuário não é administrador da agência, ou empresa sem licença | Pedir que um administrador da agência autorize | | Callback | `error=access_denied` | A agência recusou | Mostrar o estado de erro e o botão de novo | | Callback | `state` diferente | Tentativa antiga, aba duplicada ou ataque | Descartar e recomeçar pelo botão | | Troca | `400 invalid_grant` | `code` vencido (10 minutos) ou já usado, `code_verifier` errado, ou `redirect_uri` diferente | Recomeçar pelo botão, com `state` e `code_verifier` novos | | Troca | `400 invalid_request` | Faltou `code_verifier` | Enviar o `code_verifier` da sessão | | Troca | `401 invalid_client` | `client_id` ou secret errado | Conferir o `MONDE_CLIENT_SECRET` no ambiente | | Troca | `404` vazio | O `code` foi alterado | Usar o `code` decodificado da URL, sem cortar nem trocar caracteres | | API | `401` | Conexão excluída pela agência ou parceiro desativado | Apagar o token e mostrar desconectado | | API | `403` em `GET /sales` | Esperado: o parceiro não lê vendas | Nenhuma ação | | API | `400` em `POST /sales` | Faltou o cabeçalho `Idempotency-Key`, ou ele não é um UUID | Enviar um UUID v4 novo por venda | | API | `429` | Limite de requisições excedido | Esperar e repetir com intervalo crescente | | Revogação | `403 unauthorized_client` | Secret errado, ou token de outro `client_id` | Apagar o token mesmo assim e conferir o `MONDE_CLIENT_SECRET` | | API | `415` | Falta `Content-Type: application/json` | Enviar o cabeçalho em toda chamada, inclusive `GET` |