{
  "openapi": "3.1.0",
  "info": {
    "title": "Sábio Adv API",
    "version": "1.0.0",
    "summary": "API REST para integrar WhatsApp oficial, CRM jurídico, leads e funis da plataforma Sábio Adv.",
    "description": "A **Sábio Adv API** é a interface pública oficial para integrar sistemas externos com a plataforma jurídica Sábio Adv — usada por centenas de escritórios de advocacia para automatizar atendimento, qualificação de leads e gestão de funis comerciais.\n\n## O que você pode fazer\n\n- **Enviar mensagens WhatsApp** (texto livre e templates Meta aprovados) com fallback automático para Uazapi quando a janela de 24h da Meta expira.\n- **Listar templates** oficiais aprovados pela Meta vinculados à sua conta WABA.\n- **Criar leads** no CRM com tags, descrição, agendamento e coluna de destino — com merge automático por número de telefone.\n- **Atualizar nome de contato** existente no CRM.\n- **Consultar funis e colunas** disponíveis na empresa, para movimentar leads programaticamente.\n\n## Casos de uso típicos\n\n- Integração com formulários do site (Hotmart, RD Station, Typeform) → cria lead + envia template de boas-vindas.\n- CRM externo (HubSpot, Pipedrive, AdvBox) → sincroniza leads via webhook bidirecional.\n- Agentes de IA (ChatGPT, Claude, Make, n8n) → respondem WhatsApp dos clientes via API.\n- Disparos em massa controlados → respeitando limites Meta e gestão de saldo de mensagens.\n\n## Autenticação\n\nTodas as chamadas exigem o header `Authorization: Bearer <SUA_API_KEY>`. A API key é única por usuário e pode ser gerada na página **Conta → API** dentro da plataforma. Ela resolve automaticamente o WABA, phone_number_id e empresa associados ao seu perfil.\n\n## Limites e fallback\n\n- WhatsApp oficial: dentro da janela 24h da Meta, usa Cloud API.\n- Fora da janela 24h: fallback automático para Uazapi (não-oficial), preservando entrega.\n- Mensagens persistidas em `whatsapp_official_messages` para visibilidade no CRM.\n- Sem limite hard de requisições — sujeito ao saldo de mensagens da assinatura Asaas (R$ 997 base + R$ 1/processo).",
    "contact": {
      "name": "Suporte Sábio Adv",
      "url": "https://sabioadv.com.br"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://api.sabioadv.com",
      "description": "Produção"
    }
  ],
  "tags": [
    {
      "name": "Messages",
      "description": "Envio de mensagens WhatsApp (texto e templates) e listagem de templates oficiais."
    },
    {
      "name": "Conversations",
      "description": "Consulta de conversas (mensagens) trocadas com um contato."
    },
    {
      "name": "Leads",
      "description": "Criação, consulta, listagem e atualização de leads no CRM."
    },
    {
      "name": "Tags",
      "description": "Gerenciamento de tags da empresa e vínculos com leads."
    },
    {
      "name": "Funnels",
      "description": "Consulta de funis e colunas; movimentação de leads entre colunas."
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key pessoal do usuário, gerada em **Conta → API** na plataforma. Use no header `Authorization: Bearer sk_...`."
      }
    },
    "schemas": {
      "SuccessResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "message": { "type": "string" },
          "data": { "type": "object" }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": false },
          "message": { "type": "string", "example": "API key inválida" }
        }
      }
    }
  },
  "security": [{ "ApiKeyAuth": [] }],
  "paths": {
    "/functions/v1/send-message-api": {
      "post": {
        "tags": ["Messages"],
        "summary": "Enviar mensagem WhatsApp (texto, template ou listar templates)",
        "description": "Endpoint unificado para integrações externas de WhatsApp. Suporta três modos de operação via campo `type`:\n\n1. **`text`** (default quando há `content_text`): envia mensagem de texto livre. Requer janela 24h aberta com o destinatário; caso contrário, fallback automático para Uazapi.\n2. **`template`**: envia template oficial Meta aprovado. Use para reativação de conversas fora da janela 24h.\n3. **`list_templates`**: retorna todos os templates aprovados da conta WABA, com componentes e parâmetros.\n\n### Controle de IA\n\nO parâmetro `keep_ai_active` (default `false`) controla se a IA da plataforma continua respondendo após o envio. Quando `false`, a thread é marcada como `inativa` para evitar conflitos com o sistema externo.\n\n### Histórico\n\nTodas as mensagens enviadas via API são persistidas em `whatsapp_official_messages` e aparecem no Multiatendimento como mensagens enviadas pelo agente.",
        "operationId": "sendMessage",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "title": "Enviar texto",
                    "type": "object",
                    "required": ["contato", "content_text"],
                    "properties": {
                      "type": { "type": "string", "enum": ["text"], "default": "text" },
                      "contato": { "type": "string", "description": "Número do destinatário com DDI+DDD+número (apenas dígitos).", "example": "5511999998888" },
                      "content_text": { "type": "string", "description": "Texto da mensagem.", "example": "Olá! Recebemos seu contato e em breve responderemos." },
                      "keep_ai_active": { "type": "boolean", "default": false, "description": "Se `false`, marca a thread como inativa após envio." }
                    }
                  },
                  {
                    "title": "Enviar template",
                    "type": "object",
                    "required": ["contato", "template_name", "language"],
                    "properties": {
                      "type": { "type": "string", "enum": ["template"] },
                      "contato": { "type": "string", "example": "5511999998888" },
                      "template_name": { "type": "string", "example": "boas_vindas_v2" },
                      "language": { "type": "string", "example": "pt_BR" },
                      "parameters": {
                        "type": "array",
                        "items": { "type": "string" },
                        "description": "Parâmetros das variáveis {{1}}, {{2}}, etc. do template.",
                        "example": ["João", "Direito Trabalhista"]
                      },
                      "keep_ai_active": { "type": "boolean", "default": false }
                    }
                  },
                  {
                    "title": "Listar templates",
                    "type": "object",
                    "required": ["type"],
                    "properties": {
                      "type": { "type": "string", "enum": ["list_templates"] }
                    }
                  }
                ]
              },
              "examples": {
                "texto": {
                  "summary": "Envio de texto",
                  "value": { "contato": "5511999998888", "content_text": "Olá! Em breve um advogado retornará seu contato." }
                },
                "template": {
                  "summary": "Envio de template",
                  "value": { "type": "template", "contato": "5511999998888", "template_name": "boas_vindas_v2", "language": "pt_BR", "parameters": ["João"] }
                },
                "listar": {
                  "summary": "Listar templates aprovados",
                  "value": { "type": "list_templates" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Mensagem enviada com sucesso (ou templates retornados).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SuccessResponse" }
              }
            }
          },
          "400": { "description": "Parâmetros inválidos.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "401": { "description": "API key ausente ou inválida.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "404": { "description": "WABA não configurado para este usuário.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "500": { "description": "Erro interno do servidor." }
        }
      }
    },
    "/functions/v1/create-lead-api": {
      "post": {
        "tags": ["Leads"],
        "summary": "Criar lead no CRM",
        "description": "Cria um novo lead no CRM da empresa. Se já existir lead com o mesmo `contato` (número de telefone normalizado), faz **merge automático** dos campos enviados — preservando histórico e thread existentes.\n\n### Comportamento\n\n- O `contato` é normalizado para formato `55DDDNNNNNNNNN`.\n- `tags` aceita IDs (UUID) ou nomes — nomes inexistentes são criados automaticamente como `company_tags`.\n- `agendamento` cria um evento vinculado ao lead, opcionalmente com link (Google Meet, Zoom, etc.).\n- `custom_column_id` move o lead para uma coluna específica do funil (consulte via `GET /get-funnels-api`).",
        "operationId": "createLead",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["nome", "contato"],
                "properties": {
                  "nome": { "type": "string", "example": "João da Silva" },
                  "contato": { "type": "string", "description": "Telefone com DDI+DDD+número.", "example": "5511999998888" },
                  "origem": { "type": "string", "description": "Canal de origem do lead.", "example": "Site - Formulário Trabalhista" },
                  "custom_column_id": { "type": "string", "format": "uuid", "description": "ID da coluna do funil onde o lead deve ser colocado." },
                  "descricao": { "type": "string", "example": "Cliente busca orientação sobre rescisão indireta." },
                  "tags": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Array de IDs ou nomes de tags. Nomes inexistentes são criados.",
                    "example": ["Trabalhista", "Urgente"]
                  },
                  "agendamento": {
                    "type": "object",
                    "properties": {
                      "data_hora": { "type": "string", "format": "date-time", "example": "2026-06-10T14:00:00-03:00" },
                      "link": { "type": "string", "example": "https://meet.google.com/abc-defg-hij" }
                    }
                  }
                }
              },
              "examples": {
                "minimo": {
                  "summary": "Apenas obrigatórios",
                  "value": { "nome": "João da Silva", "contato": "5511999998888" }
                },
                "completo": {
                  "summary": "Lead completo com agendamento",
                  "value": {
                    "nome": "Maria Souza",
                    "contato": "5511988887777",
                    "origem": "Google Ads - Previdenciário",
                    "tags": ["Previdenciário", "Aposentadoria"],
                    "descricao": "Quer revisar benefício do INSS",
                    "agendamento": { "data_hora": "2026-06-15T10:00:00-03:00", "link": "https://meet.google.com/xyz" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Lead criado ou atualizado.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } },
          "400": { "description": "Parâmetros inválidos.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "401": { "description": "API key inválida.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/functions/v1/update-contact-name": {
      "post": {
        "tags": ["Leads"],
        "summary": "Atualizar nome de contato existente",
        "description": "Atualiza o campo `nome` de um lead existente identificado pelo número de telefone. Útil para enriquecer cadastros vindos de canais anônimos (ex: WhatsApp sem nome salvo) após qualificação.\n\nO telefone é normalizado para formato `55DDDNNNNNNNNN` e busca variantes com/sem nono dígito.",
        "operationId": "updateContactName",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["contato", "nome"],
                "properties": {
                  "contato": { "type": "string", "example": "5511999998888" },
                  "nome": { "type": "string", "example": "João da Silva" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Nome atualizado.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } },
          "401": { "description": "API key inválida." },
          "404": { "description": "Contato não encontrado." }
        }
      }
    },
    "/functions/v1/get-funnels-api": {
      "post": {
        "tags": ["Funnels"],
        "summary": "Listar funis e colunas da empresa",
        "description": "Retorna todos os funis da empresa associada à API key, com suas respectivas colunas. Use os IDs retornados para movimentar leads programaticamente via `create-lead-api` (`custom_column_id`).\n\nNão requer body — autenticação via header apenas.",
        "operationId": "getFunnels",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": { "type": "object" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lista de funis e colunas.",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "funnels": [
                    {
                      "id": "11111111-2222-3333-4444-555555555555",
                      "name": "Funil Principal",
                      "is_default": true,
                      "columns": [
                        { "id": "aaaa-bbbb-cccc-dddd", "name": "Novo Lead", "position": 0 },
                        { "id": "eeee-ffff-gggg-hhhh", "name": "Em Atendimento", "position": 1 }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": { "description": "API key inválida." },
          "404": { "description": "Usuário sem empresa associada." }
        }
      }
    },
    "/functions/v1/list-leads-api": {
      "post": {
        "tags": ["Leads"],
        "summary": "Listar leads do CRM (com filtros e paginação)",
        "description": "Lista leads da empresa associada à API key. Suporta filtros por coluna do funil, status e busca por nome/telefone. Retorna tags vinculadas a cada lead.",
        "operationId": "listLeads",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "limit": { "type": "integer", "default": 50, "minimum": 1, "maximum": 200 },
                  "offset": { "type": "integer", "default": 0, "minimum": 0 },
                  "custom_column_id": { "type": "string", "format": "uuid", "description": "Filtra leads por coluna do funil." },
                  "status": { "type": "string", "description": "Filtra leads por status (enum lead_status)." },
                  "search": { "type": "string", "description": "Busca parcial em nome ou contato (ILIKE)." }
                }
              },
              "examples": {
                "paginacao": { "summary": "Paginação simples", "value": { "limit": 50, "offset": 0 } },
                "coluna": { "summary": "Filtrar por coluna", "value": { "custom_column_id": "aaaa-bbbb-cccc-dddd" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Lista de leads." },
          "401": { "description": "API key inválida." }
        }
      }
    },
    "/functions/v1/get-lead-api": {
      "post": {
        "tags": ["Leads"],
        "summary": "Obter detalhes de um lead (por id ou telefone)",
        "description": "Retorna o lead completo (incluindo tags e agendamentos) identificado por `lead_id` ou `contato` (telefone). A busca por telefone normaliza variantes com/sem o nono dígito (móveis BR).",
        "operationId": "getLead",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lead_id": { "type": "string", "format": "uuid" },
                  "contato": { "type": "string", "example": "5511999998888" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Lead com tags e agendamentos." },
          "400": { "description": "lead_id ou contato ausentes." },
          "404": { "description": "Lead não encontrado." }
        }
      }
    },
    "/functions/v1/list-tags-api": {
      "post": {
        "tags": ["Tags"],
        "summary": "Listar tags da empresa",
        "description": "Retorna todas as `company_tags` da empresa associada à API key. Use os IDs para vincular tags a leads via `add-tag-to-lead-api`.",
        "operationId": "listTags",
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object" } } } },
        "responses": {
          "200": { "description": "Lista de tags." },
          "401": { "description": "API key inválida." }
        }
      }
    },
    "/functions/v1/list-conversations-api": {
      "post": {
        "tags": ["Conversations"],
        "summary": "Listar mensagens de uma conversa (por telefone ou lead_id)",
        "description": "Retorna o histórico de mensagens trocadas com um contato, mesclando mensagens do WhatsApp oficial Meta (`whatsapp_official_messages`) e do fallback Uazapi (`whatsapp_uazapi_messages`) em ordem cronológica ascendente.\n\nIdentifique o contato via `contato` (telefone) ou `lead_id`. Quando ambos forem omitidos, retorna 400.",
        "operationId": "listConversations",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contato": { "type": "string", "example": "5511999998888" },
                  "lead_id": { "type": "string", "format": "uuid" },
                  "limit": { "type": "integer", "default": 200, "minimum": 1, "maximum": 1000 }
                }
              },
              "examples": {
                "telefone": { "summary": "Por telefone", "value": { "contato": "5511999998888" } },
                "lead": { "summary": "Por lead_id", "value": { "lead_id": "aaaa-bbbb-cccc-dddd" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Conversa com mensagens em ordem cronológica." },
          "400": { "description": "contato ou lead_id ausentes." },
          "401": { "description": "API key inválida." }
        }
      }
    },
    "/functions/v1/add-tag-to-lead-api": {
      "post": {
        "tags": ["Tags"],
        "summary": "Adicionar tag a um lead (cria tag se não existir)",
        "description": "Vincula uma tag a um lead. O campo `tag` aceita UUID de tag existente OU nome — nomes inexistentes são criados como `company_tags`. Idempotente: vínculos duplicados são ignorados.",
        "operationId": "addTagToLead",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["lead_id", "tag"],
                "properties": {
                  "lead_id": { "type": "string", "format": "uuid" },
                  "tag": { "type": "string", "example": "Urgente" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Tag vinculada." },
          "400": { "description": "lead_id ou tag ausentes." },
          "404": { "description": "Lead não encontrado." }
        }
      }
    },
    "/functions/v1/move-lead-column-api": {
      "post": {
        "tags": ["Funnels"],
        "summary": "Mover lead para outra coluna do funil",
        "description": "Move um lead para a coluna `custom_column_id` informada. A coluna deve pertencer a um funil da mesma empresa do dono da API key.",
        "operationId": "moveLeadColumn",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["lead_id", "custom_column_id"],
                "properties": {
                  "lead_id": { "type": "string", "format": "uuid" },
                  "custom_column_id": { "type": "string", "format": "uuid" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Lead movido." },
          "400": { "description": "Parâmetros ausentes." },
          "403": { "description": "Coluna não pertence à sua empresa." },
          "404": { "description": "Lead ou coluna não encontrados." }
        }
      }
    }
  }
}

