{
  "api": "Geração de Resultados — wa-eadcom",
  "version": "1",
  "bases": {
    "site": "https://www.geracaoderesultado.com.br",
    "extensao": "/extensao",
    "bridge": "https://bridge.geracaoderesultado.com.br"
  },
  "auth": {
    "admin": {
      "header": "x-admin-pass",
      "alt": "body.adminPass",
      "description": "Senha de administrador; rotas /admin/* respondem 401 se incorreta."
    },
    "token": {
      "em": "path",
      "formato": "/api/<acao>/<TOKEN>",
      "description": "Token do assinante, gerado na extensão (trilha -> API -> botão de gerar). Identifica por qual WhatsApp o comando sai. Gerar um novo invalida o anterior."
    }
  },
  "endpoints": [
    {
      "group": "whatsapp",
      "method": "POST",
      "path": "/api/enviar-texto/{token}",
      "auth": "token",
      "body": {
        "numero": "5511988880001",
        "texto": "Olá!"
      },
      "description": "Envia mensagem de texto. Também aceita GET com os campos na query.",
      "response": {
        "ok": true
      }
    },
    {
      "group": "whatsapp",
      "method": "GET",
      "path": "/api/enviar-texto/{token}",
      "auth": "token",
      "description": "Mesma coisa por GET, com os campos na query. Útil para integrações que só disparam URL.",
      "query": {
        "numero": "5511988880001",
        "texto": "Olá!"
      },
      "response": {
        "ok": true
      }
    },
    {
      "group": "whatsapp",
      "method": "POST",
      "path": "/api/enviar-imagem/{token}",
      "auth": "token",
      "body": {
        "numero": "5511988880001",
        "url": "https://.../foto.png",
        "legenda": "opcional"
      },
      "description": "Envia imagem por url (recomendado) ou base64.",
      "response": {
        "ok": true
      }
    },
    {
      "group": "whatsapp",
      "method": "POST",
      "path": "/api/enviar-video/{token}",
      "auth": "token",
      "body": {
        "numero": "5511988880001",
        "url": "https://.../aula.mp4",
        "legenda": "opcional"
      },
      "description": "Envia vídeo. Acima de ~16 MB use enviar-documento.",
      "response": {
        "ok": true
      }
    },
    {
      "group": "whatsapp",
      "method": "POST",
      "path": "/api/enviar-documento/{token}",
      "auth": "token",
      "body": {
        "numero": "5511988880001",
        "url": "https://.../boleto.pdf",
        "nomeArquivo": "boleto.pdf"
      },
      "description": "Envia arquivo (até ~2 GB).",
      "response": {
        "ok": true
      }
    },
    {
      "group": "whatsapp",
      "method": "POST",
      "path": "/api/enviar-audio/{token}",
      "auth": "token",
      "body": {
        "numero": "5511988880001",
        "url": "https://.../audio.mp3"
      },
      "description": "Envia áudio como mensagem de voz (PTT).",
      "response": {
        "ok": true
      }
    },
    {
      "group": "whatsapp",
      "method": "GET",
      "path": "/api/listar-etiquetas/{token}",
      "auth": "token",
      "body": null,
      "description": "Lista as etiquetas do WhatsApp Business. Não envia nada.",
      "response": {
        "ok": true,
        "etiquetas": [
          {
            "id": "1",
            "nome": "Fazer Matrícula",
            "cor": "#64C4FF"
          }
        ]
      }
    },
    {
      "group": "whatsapp",
      "method": "POST",
      "path": "/api/modificar-etiquetas/{token}",
      "auth": "token",
      "body": {
        "numeros": [
          "5511988880001"
        ],
        "etiquetaId": "1",
        "acao": "adicionar"
      },
      "description": "Aplica ou remove uma etiqueta em um ou vários números.",
      "response": {
        "ok": true,
        "resultados": [
          {
            "numero": "5511988880001",
            "ok": true
          }
        ]
      }
    },
    {
      "group": "whatsapp",
      "method": "POST",
      "path": "/api/criar-nota/{token}",
      "auth": "token",
      "body": {
        "numero": "5511988880001",
        "texto": "Ligar amanhã"
      },
      "description": "Cria anotação interna no contato (o contato não vê).",
      "response": {
        "ok": true
      }
    },
    {
      "group": "whatsapp",
      "method": "POST",
      "path": "/api/apagar-mensagem/{token}",
      "auth": "token",
      "description": "Apaga uma mensagem. paraTodos=true revoga para o contato (só em mensagem própria e dentro da janela do WhatsApp); false apaga só do seu lado. Use o dados.id do webhook.",
      "body": {
        "mensagemId": "true_5511999999999@c.us_ABC123",
        "paraTodos": true
      },
      "response": {
        "ok": true,
        "paraTodos": true
      }
    },
    {
      "group": "whatsapp",
      "method": "GET",
      "path": "/api/baixar-midia/{token}",
      "auth": "token",
      "description": "Bytes de uma mídia recebida. Use o dados.id que veio no webhook — o webhook manda só metadados. Também aceita POST com os campos no corpo.",
      "query": {
        "mensagemId": "ABCD123",
        "limiteMB": "opcional; padrão e máximo 64"
      },
      "response": {
        "ok": true,
        "base64": "data:audio/ogg;base64,T2dnUw...",
        "mimetype": "audio/ogg; codecs=opus",
        "tamanho": 48213
      }
    },
    {
      "group": "whatsapp",
      "method": "GET",
      "path": "/api/listar-grupos/{token}",
      "auth": "token",
      "description": "Lista grupos e comunidades. ehComunidade=true quando o grupo tem subgrupos.",
      "response": {
        "ok": true,
        "grupos": [
          {
            "chatId": "120363000000000001@g.us",
            "nome": "Turma 2026",
            "participantes": 42,
            "ehComunidade": false,
            "subgrupos": 0
          }
        ]
      }
    },
    {
      "group": "whatsapp",
      "method": "GET",
      "path": "/api/membros-grupo/{token}",
      "auth": "token",
      "description": "Participantes de um grupo, com quem é administrador.",
      "query": {
        "chatId": "120363000000000001@g.us"
      },
      "response": {
        "ok": true,
        "membros": [
          {
            "numero": "5511988880001",
            "nome": "Ana",
            "admin": true
          }
        ]
      }
    },
    {
      "group": "whatsapp",
      "method": "POST",
      "path": "/api/criar-grupo/{token}",
      "auth": "token",
      "description": "Cria um grupo com os participantes informados.",
      "body": {
        "nome": "Turma 2026",
        "numeros": [
          "5511988880001",
          "5511988880002"
        ]
      },
      "response": {
        "ok": true,
        "chatId": "120363000000000001@g.us"
      }
    },
    {
      "group": "whatsapp",
      "method": "POST",
      "path": "/api/participantes-grupo/{token}",
      "auth": "token",
      "description": "Adiciona, remove, promove ou rebaixa participantes.",
      "body": {
        "chatId": "120363000000000001@g.us",
        "numeros": [
          "5511988880003"
        ],
        "acao": "adicionar"
      },
      "response": {
        "ok": true,
        "acao": "adicionar",
        "total": 1
      }
    },
    {
      "group": "whatsapp",
      "method": "GET",
      "path": "/api/subgrupos-comunidade/{token}",
      "auth": "token",
      "description": "Subgrupos de uma comunidade.",
      "query": {
        "chatId": "120363000000000001@g.us"
      },
      "response": {
        "ok": true,
        "subgrupos": [
          "120363000000000001@g.us"
        ]
      }
    },
    {
      "group": "extensao",
      "method": "GET",
      "path": "/extensao/api/config/:installId",
      "auth": false,
      "query": {
        "whatsapp": "número conectado (só dígitos), vincula a conta"
      },
      "response": {
        "success": true,
        "status": "ativa",
        "perfil": {
          "nome": "",
          "email": "",
          "segmento": "educacao"
        },
        "licenca": {
          "ativa": true,
          "plano": "",
          "validade": null,
          "diasRestantes": 0
        },
        "ia": {
          "ativa": false
        },
        "tutoriaisUrl": "",
        "painelUrl": "",
        "docUrl": ""
      }
    },
    {
      "group": "extensao",
      "method": "GET",
      "path": "/extensao/api/urls/install/:installId",
      "auth": false,
      "response": {
        "success": true,
        "url": ""
      }
    },
    {
      "group": "extensao",
      "method": "GET",
      "path": "/extensao/api/urls/uninstall/:installId",
      "auth": false,
      "response": "302 redirect"
    },
    {
      "group": "conta",
      "method": "POST",
      "path": "/user/register",
      "auth": false,
      "body": {
        "nome": "",
        "email": "",
        "whatsapp": "",
        "senha": "",
        "codigoLicenca": ""
      },
      "response": {
        "ok": true
      }
    },
    {
      "group": "conta",
      "method": "POST",
      "path": "/user/login",
      "auth": false,
      "body": {
        "email": "",
        "senha": ""
      },
      "response": {
        "ok": true,
        "usuario": {
          "nome": "",
          "email": "",
          "whatsapp": "",
          "plano": "",
          "diasRestantes": 0,
          "ativaAte": ""
        },
        "downloadUrl": ""
      }
    },
    {
      "group": "conta",
      "method": "POST",
      "path": "/checkout",
      "auth": false,
      "body": {
        "total": 0,
        "descricao": "",
        "email": "",
        "recorrencia": "mensal"
      },
      "response": {
        "ok": true,
        "url": "https://checkout.stripe.com/…"
      },
      "errors": [
        "stripe_nao_configurado",
        "total_invalido"
      ]
    },
    {
      "group": "conta",
      "method": "POST",
      "path": "/webhook/stripe",
      "auth": "stripe-signature",
      "note": "raw body; gera licença em checkout.session.completed",
      "response": {
        "received": true
      }
    },
    {
      "group": "admin",
      "method": "POST",
      "path": "/admin/login",
      "auth": "admin",
      "body": {
        "adminPass": ""
      },
      "response": {
        "ok": true
      }
    },
    {
      "group": "admin",
      "method": "GET",
      "path": "/admin/licencas",
      "auth": "admin",
      "response": {
        "ok": true,
        "licencas": []
      }
    },
    {
      "group": "admin",
      "method": "POST",
      "path": "/admin/licencas",
      "auth": "admin",
      "body": {
        "plano": "mensal",
        "dias": 30
      },
      "response": {
        "ok": true,
        "licenca": {
          "codigo": "",
          "plano": "",
          "dias": 30
        }
      }
    },
    {
      "group": "admin",
      "method": "GET",
      "path": "/admin/usuarios",
      "auth": "admin",
      "response": {
        "ok": true,
        "usuarios": []
      }
    },
    {
      "group": "admin",
      "method": "POST",
      "path": "/admin/usuarios",
      "auth": "admin",
      "body": {
        "nome": "",
        "email": "",
        "whatsapp": "",
        "senha": "",
        "segmento": "educacao",
        "plano": "mensal",
        "dias": 30
      },
      "response": {
        "ok": true,
        "usuario": {}
      }
    },
    {
      "group": "admin",
      "method": "DELETE",
      "path": "/admin/licencas/:id",
      "auth": "admin",
      "response": {
        "ok": true
      }
    },
    {
      "group": "admin",
      "method": "DELETE",
      "path": "/admin/usuarios/:id",
      "auth": "admin",
      "note": "libera a licença que estava com o usuário",
      "response": {
        "ok": true
      }
    },
    {
      "group": "dados",
      "method": "GET",
      "path": "/admin/dados",
      "auth": "admin",
      "note": "lista as tabelas e o schema",
      "response": {
        "ok": true,
        "tabelas": [],
        "schema": {}
      }
    },
    {
      "group": "dados",
      "method": "GET",
      "path": "/admin/dados/:tabela",
      "auth": "admin",
      "note": "filtros por querystring (ex.: ?cliente=<id>)",
      "response": {
        "ok": true,
        "registros": []
      }
    },
    {
      "group": "dados",
      "method": "POST",
      "path": "/admin/dados/:tabela",
      "auth": "admin",
      "response": {
        "ok": true,
        "registro": {}
      }
    },
    {
      "group": "dados",
      "method": "PUT",
      "path": "/admin/dados/:tabela/:id",
      "auth": "admin",
      "response": {
        "ok": true,
        "registro": {}
      }
    },
    {
      "group": "dados",
      "method": "DELETE",
      "path": "/admin/dados/:tabela/:id",
      "auth": "admin",
      "response": {
        "ok": true
      }
    },
    {
      "group": "admin",
      "method": "GET",
      "path": "/admin/config",
      "auth": "admin",
      "response": {
        "ok": true,
        "config": {}
      }
    },
    {
      "group": "admin",
      "method": "POST",
      "path": "/admin/config",
      "auth": "admin",
      "body": {
        "config": {
          "status": "ativa",
          "downloadUrl": "",
          "checkoutUrl": "",
          "tutoriaisUrl": "",
          "docUrl": ""
        }
      },
      "response": {
        "ok": true,
        "config": {}
      }
    }
  ],
  "erros": {
    "token_invalido": "401 — token desconhecido, revogado ou licença vencida",
    "nenhuma_extensao_conectada": "WhatsApp Web do assinante fechado ou API desligada",
    "numero_nao_existe_no_whatsapp": "o número não tem conta no WhatsApp",
    "numero_obrigatorio": "campo ausente; a resposta traz camposRecebidos e camposAceitos",
    "acao_invalida": "ação de participantes fora de adicionar/remover/promover/rebaixar",
    "chatId_obrigatorio": "faltou o id do grupo ou comunidade",
    "download_falhou": "o servidor da mídia não respondeu ou devolveu erro (o detalhe traz o status)",
    "download_timeout": "a mídia demorou mais que o limite para baixar",
    "arquivo_muito_grande": "mídia acima de LIMITE_DOWNLOAD_MB (64 MB por padrão)",
    "url_invalida": "url malformada",
    "url_protocolo_invalido": "use http:// ou https://",
    "arquivo_vazio": "a url respondeu sem conteúdo",
    "mensagem_nao_encontrada": "o id não existe no WhatsApp conectado, ou está fora do formato de id de mensagem",
    "muito_grande": "mídia acima do teto pedido (vem com tamanho e limiteMB)",
    "mensagemId_obrigatorio": "faltou o id da mensagem em baixar-midia",
    "sem_midia_nesta_mensagem": "a mensagem existe mas não tem mídia (ex.: texto)",
    "download_da_midia_falhou": "a mensagem tem mídia, mas o download falhou; o detalhe traz o motivo real",
    "revoke_so_para_mensagem_propria": "paraTodos só vale para mensagem que você mesmo enviou",
    "fora_da_janela": "o WhatsApp não permite mais revogar esta mensagem (antiga demais)",
    "apagar_falhou": "o apagamento falhou; o detalhe traz o motivo"
  },
  "limites": {
    "imagem_video_audio_MB": 16,
    "documento_GB": 2,
    "base64_MB": 45,
    "observacao": "os 16 MB e 2 GB são do WhatsApp. Por url, o bridge baixa no servidor (teto LIMITE_DOWNLOAD_MB, 64 por padrão) e entrega em base64 à extensão — os bytes passam pelo bridge.",
    "download_url_MB": 64
  },
  "camposAlternativos": {
    "numero": [
      "numero",
      "telefone",
      "phone",
      "number",
      "to",
      "celular",
      "whatsapp"
    ],
    "texto": [
      "texto",
      "mensagem",
      "message",
      "text",
      "body",
      "msg"
    ],
    "observacao": "O número aceita qualquer formatação — só os dígitos são usados.",
    "chatId": [
      "chatId",
      "grupo",
      "groupId"
    ],
    "destinoGrupo": "Nas rotas de envio, um id terminado em @g.us (grupo/comunidade) OU @lid em chatId/grupo manda para aquele chat, em vez de um número. Use o @lid para responder conversa que chegou sem telefone resolvido.",
    "mensagemId": [
      "mensagemId",
      "id",
      "messageId"
    ]
  },
  "webhooks": {
    "descricao": "A extensão faz POST na URL configurada (trilha -> WebHook) a cada evento marcado. Content-Type: application/json. Responda 2xx.",
    "garantido": [
      "evento",
      "timestamp"
    ],
    "opcionais": {
      "observacao": "Só chegam se o assinante marcar o campo em \"Dados a enviar\" daquele webhook. Trate todos como ausentes por padrão.",
      "dados_evento": "dados",
      "numero": "numero",
      "nome": "nome",
      "foto": "foto",
      "etiqueta": "etiqueta",
      "perfil_contato": "perfil",
      "usuario_logado": "usuario",
      "lid": "lid — vai junto do numero (mesma marcação): a identidade do contato na forma nova do WhatsApp"
    },
    "eventos": {
      "mensagem_recebida": "{ id, texto (legenda quando for midia), tipo, ts, direcao } (+ midia com metadados)",
      "mensagem_enviada": "idem, direcao=enviada",
      "etiqueta": "{ etiquetaId, abaId }",
      "crm": "mudança de etapa no funil",
      "respostas_rapidas": "{ atalho, titulo }",
      "encerrar_atendimento": "sem dados adicionais",
      "agendamento": "{ texto, id, recorrencia }",
      "auto_atendimento": "{ regraId, via } ou { via: \"ia\", agente }",
      "follow_up": "{ sequencia, passo }",
      "mensagem_apagada": "{ id, direcao, paraTodos, ts } — id é o da mensagem original; direcao diz se era sua ou do contato. Dispara inclusive quando apagam direto no celular."
    },
    "naoUsar": {
      "anuncio_instagram": "declarado mas nunca emitido hoje",
      "status": "emitido internamente, mas não pode ser assinado — nunca chega a um webhook"
    },
    "entrega": {
      "cabecalhos": "X-GR-Evento, X-GR-Entrega (id da entrega), X-GR-Tentativa; com chave configurada também X-GR-Timestamp e X-GR-Signature",
      "assinatura": "HMAC-SHA256 de '<X-GR-Timestamp>.<corpo cru>', enviado em X-GR-Signature: sha256=<hex>. Configure a chave em trilha → WebHook → Chave secreta. Sem chave, não vem assinatura — proteja ao menos com um segredo no caminho da URL",
      "conferir": "recalcule o HMAC sobre timestamp + '.' + corpo CRU (antes do parse), compare com hash_equals/timingSafeEqual e recuse timestamp com mais de 5 minutos",
      "retentativa": "1, 2, 10 e 30 minutos depois da falha (5 tentativas contando a primeira). Retenta em 5xx, 429, 408 e queda de rede; NÃO retenta nos demais 4xx (o destino recusou). A fila fica em disco e sobrevive a fechar o navegador",
      "dedupe": "X-GR-Entrega é o MESMO em todas as tentativas — use para não gravar o evento duas vezes; responda 2xx rápido e processe em fila",
      "log": "últimos 30 envios ficam na extensão, com status, corpo, quando sai a próxima tentativa e quando desistimos"
    },
    "exemplo": {
      "evento": "mensagem_recebida",
      "timestamp": "2026-07-22T14:40:00.000Z",
      "numero": "5554999480043",
      "nome": "Vitória Eduarda",
      "foto": "https://...",
      "etiqueta": "Estágios",
      "perfil": {
        "isBusiness": false
      },
      "dados": {
        "id": "ABCD",
        "texto": "Olá",
        "tipo": "chat",
        "ts": 1784750700,
        "direcao": "recebida"
      },
      "usuario": {
        "numero": "551133387242"
      },
      "lid": "8367082868968"
    },
    "midia": "dados.midia traz só metadados. Os bytes vêm de GET /api/baixar-midia/{token}?mensagemId=<dados.id>, sob demanda.",
    "lid": "O WhatsApp novo identifica contatos por <digitos>@lid (15 a 17 digitos), que NAO e telefone. A extensao resolve antes de disparar. Se nao conseguir, numero vem null — nunca o LID. Trate numero:null como contato sem telefone conhecido.",
    "direcao": "dados.direcao é derivada da chave da mensagem (prefixo true_/false_), então o eco de mensagem enviada vem como enviada e dispara mensagem_enviada.",
    "texto": "Em mensagem de texto, dados.texto e o conteudo. Em MIDIA, dados.texto e a LEGENDA (vazia quando nao ha) — a miniatura em base64 que o WhatsApp guarda junto nao e enviada."
  }
}