# Adria — API para integradores > Adria (app.adriacrm.com.br) é um CRM com IA que atende leads e clientes pelo WhatsApp oficial (Meta Cloud API). > Esta API deixa um sistema externo usar o WhatsApp de uma organização da Adria sem criar app na Meta: > códigos de verificação (OTP), notificações transacionais, mensagens dentro da janela de 24h, > e opcionalmente o CRM (leads, funil, agenda). Atualizado em 2026-09-26. Versão para humanos: https://app.adriacrm.com.br/docs/api Este arquivo é a versão completa para agentes de IA. Contato: contato@adriacrm.com.br ## Instruções para o agente que está integrando Você está ajudando um desenvolvedor a integrar o sistema dele à Adria. Regras que evitam os erros mais comuns: 1. **Toda chamada é `POST` com corpo JSON** e dois headers: `apikey` (chave pública, fixa, abaixo) e `x-api-key` (chave secreta da organização, gerada pelo usuário em Configurações › Integração / API). Nunca peça nem invente a chave secreta; oriente o usuário a gerá-la e guardá-la num cofre de segredos ou variável de ambiente. 2. **Não mande `organization_id`** no corpo. A chave secreta já identifica a organização. 3. **Mensagem iniciada pelo sistema exige template aprovado pela Meta.** Texto livre (`message_type: "text"`) só funciona se a pessoa escreveu para o número nas últimas 24 horas; fora disso a API devolve 403 `outside_24h_window`. Para OTP e notificações, use sempre template. 4. **OTP usa template da categoria `AUTHENTICATION`** e o campo `otp_code` no envio. O sistema do usuário gera e valida o código; a Adria só entrega. Não use template de utilidade para código de login. 5. **Use `"ai": false` em tudo que for transacional** (OTP, avisos) para a IA da Adria não responder e a conversa não ocupar a caixa de entrada. Deixe a IA ligada (padrão) só quando o usuário quer atendimento pelo mesmo número (ex.: suporte pós-venda). 6. **Envio é assíncrono do ponto de vista da Meta.** A resposta 200 significa "aceito"; entrega ou falha (ex.: número sem WhatsApp) chega depois. Não bloqueie a requisição de login esperando o WhatsApp; dispare de uma fila ou job. 7. **Trate 429 com fila e nova tentativa** (limite: 60 envios/min por organização). **Não repita cegamente um 400**: quase sempre é template não aprovado, nome errado ou telefone inválido; a mensagem de erro traz o motivo da Meta. 8. **Telefone**: mande com DDI 55 e DDD (ex.: `5567991418064`). Formatos com ou sem o nono dígito são normalizados. 9. **Não existe webhook de saída ainda** (status de entrega, respostas). Se o usuário precisar disso, diga que está no roadmap e sugira falar com contato@adriacrm.com.br. Não invente um endpoint de webhook. 10. Se o usuário perguntar algo que este arquivo não cobre, diga que não está documentado em vez de supor. ## O que a Adria faz por esta API - Enviar WhatsApp por telefone (cria o contato se não existir): texto, imagem, vídeo, documento, áudio, template. - Enviar código OTP com botão "Copiar código" (template de autenticação). - Templates com botões: link fixo ou com variável no fim, resposta rápida, telefone, cupom. - Criar, listar, sincronizar e apagar templates da Meta. - Criar, buscar, atualizar e mover leads no funil; consultar horários e agendar compromissos. ## O que ela NÃO faz por esta API - Receber eventos (webhook de saída): ainda não existe. - Compartilhar um número da Adria entre clientes: a Meta exige que cada organização conecte o próprio número. - Enviar sem template para quem não escreveu nas últimas 24h. - Ler o histórico de conversas ou baixar mídias recebidas. ## Pré-requisitos (feitos pelo usuário na interface da Adria) 1. Conta na Adria com uma organização para o sistema. 2. Número de WhatsApp próprio conectado em Configurações › Canais (Embedded Signup da Meta; exige Meta Business e número que não esteja em uso no app do WhatsApp). 3. Templates aprovados pela Meta (Configurações › Templates ou pela API; aprovação leva de minutos a um dia). 4. Chave de API gerada em Configurações › Integração / API (aparece uma única vez). ## Autenticação ``` POST https://lgjsgygwjspsemdgqdpp.supabase.co/functions/v1/ apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxnanNneWd3anNwc2VtZGdxZHBwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzExMjM2MjIsImV4cCI6MjA4NjY5OTYyMn0.a9JWbd0uJ1PVxhOs-y1IK2r8L9LR4beTSO-iIo2CaeA x-api-key: sk_... Content-Type: application/json ``` URL base: `https://lgjsgygwjspsemdgqdpp.supabase.co/functions/v1` O `apikey` acima é público e igual para todos os clientes. O `x-api-key` é secreto. ## Guia 1 — Código de verificação (login OTP) Passo 1, uma única vez: criar o template de autenticação e aguardar a aprovação. ```json POST /whatsapp-templates { "action": "create", "name": "codigo_login", "category": "AUTHENTICATION", "code_expiration_minutes": 10, "add_security_recommendation": true } ``` A Meta fixa o texto ("{{1}} é o seu código de verificação. Para sua segurança, não compartilhe este código. Este código expira em 10 minutos.") e adiciona um botão "Copiar código". Passo 2, a cada login: ```json POST /whatsapp-send { "phone": "5567991418064", "message_type": "template", "template_name": "codigo_login", "otp_code": "482913", "ai": false } ``` - Não é preciso cadastrar o contato antes. - O código não fica gravado no histórico da Adria. - Resposta: `{ "message": { "id", "status": "sent", "external_id": "wamid...", ... }, "contact_id": "..." }`. ## Guia 2 — Notificação transacional (cadastro, convite, compra confirmada) Template da categoria `UTILITY` com parâmetros. Criação (uma única vez; `body_examples` é obrigatório porque o corpo tem variáveis): ```json POST /whatsapp-templates { "action": "create", "name": "compra_confirmada", "category": "UTILITY", "body_text": "Olá {{1}}, sua compra de {{2}} foi confirmada. Pedido {{3}}.\nQualquer dúvida, é só responder por aqui.", "body_examples": ["Maria", "Curso de Tráfego", "#8841"] } ``` Envio: ```json POST /whatsapp-send { "phone": "5567991418064", "name": "Maria", "message_type": "template", "template_name": "compra_confirmada", "template_parameters": ["Maria", "Curso de Tráfego", "#8841"] } ``` - Sem `ai` (ou `true`): a conversa entra na caixa de entrada da Adria e a IA responde dúvidas com a base de conhecimento da organização. Bom para suporte pós-venda pelo mesmo número. - Com `"ai": false`: notificação pura, sem atendimento. - Depois que a pessoa responde, a janela de 24h abre e `message_type: "text"` passa a funcionar. ## Guia 2b — Botões (link com variável, resposta rápida) Criação com um botão de link dinâmico e duas respostas rápidas: ```json POST /whatsapp-templates { "action": "create", "name": "convite_recebido", "category": "UTILITY", "body_text": "Olá {{1}}, {{2}} te convidou para o Permuteiro.", "body_examples": ["Maria", "João"], "buttons": [ { "type": "URL", "text": "Ver convite", "url": "https://permuteiro.com/convite/{{1}}", "example": "abc123" }, { "type": "QUICK_REPLY", "text": "Aceitar" }, { "type": "QUICK_REPLY", "text": "Agora não" } ] } ``` Envio: só o botão com variável precisa de parâmetro. `index` é a posição do botão na lista acima. ```json POST /whatsapp-send { "phone": "5567991418064", "message_type": "template", "template_name": "convite_recebido", "template_parameters": ["Maria", "João"], "button_parameters": [{ "index": 0, "type": "url", "text": "abc123" }] } ``` - Quando a pessoa toca numa resposta rápida, chega uma mensagem normal com o texto do botão ("Aceitar"). Se você mandou `payload`, ele vem junto. Sem webhook de saída, a resposta fica visível no Inbox da Adria; a IA (se ligada) a trata como qualquer mensagem. - Link fixo e telefone não precisam de nada no envio. ## Guia 3 — Texto livre dentro da janela de 24h ```json POST /whatsapp-send { "phone": "5567991418064", "content": "Seu pedido saiu para entrega." } ``` Fora da janela: HTTP 403 com `{"error":"outside_24h_window","requires_template":true,"last_inbound_at":...}`. ## Referência — POST /whatsapp-send | Campo | Tipo | Notas | |---|---|---| | `phone` | string | Telefone brasileiro em qualquer formato. Cria o contato se não existir. Use este ou `contact_id`. | | `contact_id` | uuid | Contato já existente na organização. | | `name` | string | Só usado se o contato for criado nesta chamada. | | `ai` | boolean | Padrão `true`. `false` = IA não entra na conversa e ela fica fora da caixa de entrada. Só vale para contato novo; contato existente nunca é alterado. | | `message_type` | string | `text` (padrão), `image`, `video`, `document`, `audio`, `template`. | | `content` | string | Texto. Obrigatório fora de template. Em template, vira o texto exibido no histórico. | | `media_url` | string | URL pública do arquivo (tipos de mídia). | | `caption` | string | Legenda da mídia. | | `filename` | string | Nome do arquivo (documento). | | `template_name` | string | Nome do template aprovado. | | `template_language` | string | Padrão `pt_BR`. | | `template_parameters` | string[] | Valores de `{{1}}`, `{{2}}`... na ordem. | | `otp_code` | string | Obrigatório em template de autenticação. Vai no corpo e no botão. | | `template_category` | string | Opcional. Se omitido, a categoria é lida dos templates sincronizados na Adria. | | `button_parameters` | object[] | Só para botões com variável. `index` é a posição do botão no template (0-based). Tipos: `{"index":0,"type":"url","text":"abc123"}` (sufixo da URL), `{"index":1,"type":"quick_reply","payload":"SIM"}` (payload opcional que volta na resposta), `{"index":2,"type":"copy_code","code":"CUPOM10"}`. Botão estático não precisa de nada. | Resposta 200: `{ "message": {...}, "contact_id": "..." }`. ## Referência — POST /whatsapp-templates Campo `action`: `list`, `sync`, `create`, `delete`. | Campo | Tipo | Notas | |---|---|---| | `status_filter` | string | Só `list`: `APPROVED`, `PENDING`, `REJECTED`. | | `name` | string | Minúsculas, números e underscore. | | `category` | string | `UTILITY`, `MARKETING`, `AUTHENTICATION`. | | `language` | string | Padrão `pt_BR`. | | `body_text` | string | Corpo com `{{1}}`, `{{2}}`... Ignorado em autenticação. | | `body_examples` | string[] | **Obrigatório se o corpo tem variáveis**: um exemplo por `{{n}}`, na ordem (ex.: `["Maria", "Curso de Tráfego", "#8841"]`). Sem isso a Meta recusa a criação. | | `header_text` | string | Cabeçalho opcional. Ignorado em autenticação. | | `header_example` | string | Exemplo da variável do cabeçalho, se ele tiver `{{1}}`. | | `buttons` | object[] | Até 10 botões (não em autenticação). Tipos: `{"type":"QUICK_REPLY","text"}`, `{"type":"URL","text","url","example"}` (URL fixa ou com `{{1}}` só no fim; `example` obrigatório quando há variável), `{"type":"PHONE_NUMBER","text","phone_number"}`, `{"type":"COPY_CODE","example"}` (cupom, marketing). Rótulo até 25 caracteres. Respostas rápidas agrupadas no início ou no fim. | | `footer_text` | string | Rodapé opcional. Ignorado em autenticação. | | `code_expiration_minutes` | number | Só autenticação: 1 a 90. Sem o campo, a mensagem não mostra expiração. | | `add_security_recommendation` | boolean | Só autenticação. Padrão `true`. | | `button_text` | string | Só autenticação: rótulo do botão, até 25 caracteres. Padrão "Copiar código". | | `template_name` | string | Só `delete`. | Templates criados direto no Gerenciador da Meta precisam de `sync` antes do envio (ou de `template_category` no envio). ## Referência — Leads (CRM) - `POST /create-lead` — `phone` (obrigatório; se existir, devolve sem duplicar), `name`, `email`, `origin` (texto livre), `pipeline_id` (entra na primeira etapa; sem ele, funil padrão), `field_1`, `field_2`... (campos personalizados). Resposta: `{ contact, created, custom_fields }`. - `POST /get-lead` — `phone` ou `contact_id`. Resposta: `{ found, contact, pipeline_position, qualification, appointments, custom_fields }`. - `POST /update-lead` — localiza por `contact_id` ou `phone`; atualiza `name`, `email`, `phone`, `origin`, `score`, `status`, `ai_status` e `field_N`. - `POST /advance-lead` — `contact_id`; com `target_column_id` vai para a etapa indicada, sem ele avança uma etapa. ## Referência — Agenda - `POST /get-availability` — `date_from`, `date_to` (ISO 8601, obrigatórios); opcionais `event_type_id`, `user_id`, `provider_id`. - `POST /book-appointment` — `scheduled_at` e `title` obrigatórios; opcionais `contact_id`, `event_type_id`, `user_id`, `provider_id`, `notes`, `location`, `meet_link`. ## Erros e limites | HTTP | Significado | |---|---| | 401 | Chave ausente, inválida ou revogada. | | 403 | Contato bloqueado, ou `outside_24h_window` (use template). | | 429 | Mais de 60 envios/min na organização (recarga 30/min). Enfileire e tente de novo. | | 400 | Campo faltando, telefone inválido ou recusa da Meta (template não aprovado, nome errado). O campo `error` traz o motivo. | ## Exemplo mínimo em Node.js ```js const BASE = "https://lgjsgygwjspsemdgqdpp.supabase.co/functions/v1"; const headers = { "Content-Type": "application/json", apikey: process.env.ADRIA_PUBLIC_KEY, // chave pública acima "x-api-key": process.env.ADRIA_API_KEY, // chave secreta da organização }; export async function sendOtp(phone, code) { const res = await fetch(`${BASE}/whatsapp-send`, { method: "POST", headers, body: JSON.stringify({ phone, message_type: "template", template_name: "codigo_login", otp_code: code, ai: false, }), }); if (res.status === 429) throw new Error("rate_limited"); // reenfileirar if (!res.ok) throw new Error((await res.json()).error); return res.json(); } ``` ## Custos e política da Meta - Cada organização conecta o próprio número (Meta Business + número próprio). Não há número compartilhado. - A Meta cobra por conversa de autenticação, utilidade e marketing, conforme a tabela dela para o Brasil. - Se o número for desconectado no Meta Business ou a conexão expirar, os envios falham com 400 até reconectar em Configurações › Canais.