{"openapi":"3.0.3","info":{"title":"AutoZap Public API","version":"1.0.0","description":"API pública para integração com o AutoZap. Autentique via header `X-API-Key` ou `Authorization: Bearer <key>`.","contact":{"name":"Suporte AutoZap","url":"https://autozap.tech/ajuda"}},"servers":[{"url":"https://api.autozap.tech/api/public","description":"Produção"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"}},"schemas":{"BatchItem":{"type":"object","properties":{"id":{"type":"string","example":"213873838","description":"ID externo do seu sistema. Retornado na resposta como external_id."},"to":{"type":"string","example":"5565993282674","description":"Telefone E.164 sem +"},"template":{"type":"string","example":"004_preventivo_parc_vence_hj","description":"Nome do template cadastrado em /templates. Mutuamente exclusivo com message."},"message":{"type":"string","description":"Texto inline da mensagem. Sobrepõe template se ambos forem informados."},"vars":{"type":"array","description":"Variáveis de personalização. Aceita array [{type, key, value}] ou objeto {key: value}.","items":{"type":"object","properties":{"type":{"type":"string","example":"text"},"key":{"type":"string","example":"dev.nome"},"value":{"type":"string","example":"TAMYRIS VITORIA GONCALVES SANTANA"}}}},"campaign":{"type":"string","example":"Cobrança Junho — Flamboyan","description":"Nome da campanha em que esta mensagem será agrupada no painel. Itens com o mesmo campaign (e mesma origem) caem na mesma campanha. Se omitido, a campanha é nomeada como batch:<template>."},"media_url":{"type":"string","format":"uri","example":"https://meusistema.com/boletos/214140190.pdf","description":"URL pública de um anexo individual por destinatário (PDF, imagem, vídeo, áudio). O texto (message/template) vira a legenda. Pode ser enviado sozinho (sem message/template)."},"media_type":{"type":"string","enum":["image","video","audio","document"],"example":"document","description":"Tipo do media_url. Se omitido, é inferido pela extensão da URL (ex: .pdf → document)."},"from":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"example":"5565999990000","description":"Número(s) de origem (E.164 sem +). String = fixa o envio na instância conectada com esse número. Array = distribui o lote (rodízio/round-robin) entre as instâncias conectadas com esses números — rodízio de chips, reduz risco de banimento. Vazio = primeira instância conectada."},"team":{"type":"string","example":"Gazin","description":"Equipe de disparo (nome ou UUID, obtido em GET /teams). Usa TODOS os chips conectados da equipe em rodízio. Tem prioridade sobre o `team` do lote; `from` no item sobrepõe a equipe."}},"example":{"id":"213873838","to":"5565993282674","from":"","template":"004_preventivo_parc_vence_hj","campaign":"Cobrança Junho — Flamboyan","vars":[{"type":"text","key":"var1","value":"Flamboyan"},{"type":"text","key":"dev.nome","value":"TAMYRIS VITORIA GONCALVES SANTANA"}]}},"ProductBulkItem":{"type":"object","required":["name"],"properties":{"id":{"type":"string","example":"SKU-00123","description":"ID externo do seu sistema. Retornado na resposta como external_id."},"name":{"type":"string","example":"Arroz Branco 5kg"},"description":{"type":"string","example":"Arroz tipo 1, pacote de 5kg"},"price":{"type":"number","example":24.9},"cost_price":{"type":"number","example":18.5,"description":"Preço de custo (para cálculo de margem)"},"category":{"type":"string","example":"Mercearia"},"unit":{"type":"string","example":"un","description":"Unidade de medida (un, kg, L, etc.)"},"ncm":{"type":"string","example":"1006.30.21","description":"Código NCM (classificação fiscal)"},"cest":{"type":"string","example":"17.036.00","description":"Código CEST (substituição tributária)"},"barcode":{"type":"string","example":"7891234567895","description":"Código de barras EAN/GTIN. Usado para casar com produto já existente."},"stock_quantity":{"type":"number","example":120,"description":"Quantidade em estoque. Se informado, gera movimentação de estoque (entrada ou ajuste)."},"stock_min":{"type":"number","example":10,"description":"Estoque mínimo para alerta"},"available":{"type":"boolean","example":true}},"example":{"id":"SKU-00123","name":"Arroz Branco 5kg","price":24.9,"category":"Mercearia","unit":"un","ncm":"1006.30.21","barcode":"7891234567895","stock_quantity":120,"stock_min":10}}}},"paths":{"/messages/send":{"post":{"summary":"Enviar mensagem WhatsApp (texto ou mídia)","operationId":"sendMessage","description":"Envia mensagem de texto ou mídia (imagem, vídeo, documento, áudio). Informe `message` para texto ou `media_url` para mídia. Ambos podem ser usados juntos (media com legenda).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to"],"properties":{"to":{"type":"string","example":"5511999999999","description":"Número E.164 sem +"},"message":{"type":"string","example":"Olá! Segue o boleto."},"media_url":{"type":"string","format":"uri","example":"https://exemplo.com/boleto.pdf","description":"URL pública da mídia — PDF, imagem, vídeo ou áudio. O texto (message/caption) vira a legenda."},"media_type":{"type":"string","enum":["image","video","document","audio"],"description":"OPCIONAL. Tipo da mídia. Se omitido, é inferido pela extensão da URL (ex: .pdf → document, .mp4 → video). Só informe para forçar um tipo específico."},"caption":{"type":"string","description":"Legenda da mídia (substitui message quando media_url presente)"},"instance_id":{"type":"string","format":"uuid","description":"ID da instância (opcional — usa a primeira conectada)"}}}}}},"responses":{"200":{"description":"Enviado","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"message_id":{"type":"string"}}}}}},"400":{"description":"to ausente ou nem message nem media_url informados"},"422":{"description":"Nenhuma instância WhatsApp conectada"}}}},"/messages/{id}":{"get":{"summary":"Status de mensagem individual","operationId":"getMessage","description":"Retorna o status atual de uma mensagem pelo seu `message_id` (retornado em /messages/send ou /messages/batch).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Dados da mensagem","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"phone":{"type":"string"},"status":{"type":"string","enum":["pending","sending","sent","error","skipped"]},"external_id":{"type":"string","description":"ID externo informado no envio do batch"},"sent_at":{"type":"string","format":"date-time"},"error_message":{"type":"string"},"retry_count":{"type":"integer"},"campaign_id":{"type":"string","format":"uuid"}}}}}},"404":{"description":"Mensagem não encontrada ou não pertence a este tenant"}}}},"/phone/validate":{"post":{"summary":"Validar se número(s) têm WhatsApp","operationId":"validatePhones","description":"Verifica se um ou mais números de telefone possuem conta WhatsApp ativa. Use antes de disparar campanhas para reduzir falhas e risco de banimento.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phones"],"properties":{"phones":{"oneOf":[{"type":"array","items":{"type":"string"},"maxItems":200,"example":["5511999999999","5521888888888"]},{"type":"string","example":"5511999999999"}]}}}}}},"responses":{"200":{"description":"Resultado por número","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"phone":{"type":"string"},"exists":{"type":"boolean","description":"true = tem WhatsApp ativo"},"whatsapp_id":{"type":"string","description":"JID do WhatsApp (ex: 5511999999999@s.whatsapp.net)"}}}}}}}}},"400":{"description":"phones ausente ou acima de 200"},"422":{"description":"Nenhuma instância conectada para fazer a verificação"}}}},"/pipelines":{"get":{"summary":"Listar pipelines e stages","operationId":"listPipelines","description":"Retorna os pipelines configurados no tenant com seus stages aninhados. Use o `id` do stage como `stage_id` ao criar deals.","responses":{"200":{"description":"Lista de pipelines","content":{"application/json":{"schema":{"type":"object","properties":{"pipelines":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Cobranças"},"type":{"type":"string"},"stages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Use como stage_id ao criar deal"},"name":{"type":"string","example":"Negociação"},"position":{"type":"integer"},"is_won":{"type":"boolean","description":"true = stage de vitória (deal ganho)"},"is_lost":{"type":"boolean","description":"true = stage de derrota (deal perdido)"}}}}}}}}}}}}}}},"/campaigns":{"get":{"summary":"Listar campanhas","operationId":"listCampaigns","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["active","scheduled","paused","completed"]},"description":"Filtrar por status"},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Lista de campanhas com stats","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"campaigns":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string"},"campaign_type":{"type":"string"},"total":{"type":"integer"},"sent":{"type":"integer"},"pending":{"type":"integer"},"error":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time"}}}}}}}}}}}},"/contacts":{"get":{"summary":"Listar contatos","operationId":"listContacts","parameters":[{"name":"search","in":"query","schema":{"type":"string"},"description":"Busca por nome ou telefone"},{"name":"tag","in":"query","schema":{"type":"string"},"description":"Filtrar por tag"},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":500}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Lista de contatos","content":{"application/json":{"schema":{"type":"object","properties":{"contacts":{"type":"array"},"total":{"type":"integer"}}}}}}}},"post":{"summary":"Criar ou atualizar contato","operationId":"upsertContact","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone"],"properties":{"phone":{"type":"string","example":"5511999999999"},"name":{"type":"string","example":"João Silva"},"email":{"type":"string","format":"email"},"tags":{"type":"array","items":{"type":"string"},"example":["lead","vip"]}}}}}},"responses":{"200":{"description":"Contato criado ou atualizado"}}}},"/conversations":{"get":{"summary":"Listar conversas","operationId":"listConversations","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["open","resolved","pending"],"default":"open"}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Lista de conversas","content":{"application/json":{"schema":{"type":"object","properties":{"conversations":{"type":"array"},"total":{"type":"integer"}}}}}}}}},"/campaigns/create":{"post":{"summary":"Criar campanha (padrão ou IA conversacional)","operationId":"createCampaign","description":"Cria uma campanha WhatsApp padrão ou de IA conversacional. Para campanhas de IA (`campaign_type: conversational_ai`), a persona responde automaticamente cada contato. Requer plano Pro ou Business. Quando nem `team` nem `from`/`instance_id` forem informados, tenta inferir a equipe automaticamente pelo nome da campanha (ex: campanha \"GAZIN_JUNHO\" casa com a equipe \"Gazin\" cadastrada no tenant) antes de cair na 1ª instância conectada do tenant.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","message"],"properties":{"name":{"type":"string","example":"Negociação Junho 2026"},"message":{"type":"string","example":"Olá {nome}, temos uma proposta especial para você!"},"campaign_type":{"type":"string","enum":["standard","conversational_ai"],"default":"standard","description":"Tipo de campanha. `conversational_ai` ativa IA para responder cada contato."},"ai_persona_id":{"type":"string","format":"uuid","description":"UUID da persona de IA (obrigatório quando campaign_type=conversational_ai). Obtenha no painel em Agentes IA."},"phones":{"type":"array","items":{"type":"string"},"example":["5511999999999"],"description":"Telefones E.164 sem + (opcional)."},"from":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"example":["5511988880001","5511988880002","5511988880003"],"description":"Número(s) de origem (chip). String = fixa um chip; array = define a ESCALA (rodízio entre esses chips). Os contatos são distribuídos automaticamente entre os chips. Editável depois no painel (pool de instâncias). Omitido = primeira instância conectada."},"team":{"type":"string","example":"Gazin","description":"Equipe de disparo (nome ou UUID, obtido em GET /teams). Congela TODOS os chips conectados da equipe como ESCALA da campanha (ex: equipe Gazin com 20 chips → rodízio entre os 20). Tem prioridade sobre `from` e `instance_id`."},"pool_batch_size":{"type":"integer","default":2,"description":"Quantas mensagens cada chip envia antes de passar para o próximo no rodízio (só quando `from` é array)."},"instance_id":{"type":"string","format":"uuid","description":"ID da instância WhatsApp por UUID (opcional — alternativa a `from`; usa a primeira conectada se ambos omitidos)"},"schedule_at":{"type":"string","format":"date-time","description":"Data/hora de início (opcional). Com data futura, a campanha fica \"agendada\" e dispara no horário. Sem schedule_at, a campanha é criada como RASCUNHO e só dispara após ativação no painel."},"mask_sensitive_data":{"type":"boolean","default":false,"description":"Quando true, mascara CPF/CNPJ formatados no texto enviado (ex: 061.***.***-01)."}}}}}},"responses":{"200":{"description":"Campanha criada","content":{"application/json":{"schema":{"type":"object","properties":{"campaign_id":{"type":"string","format":"uuid"},"added":{"type":"integer","description":"Número de contatos adicionados com sucesso"},"pool":{"type":"integer","nullable":true,"description":"Quantidade de chips na escala (null quando chip único)"}}}}}},"400":{"description":"Campos obrigatórios ausentes (name, message ou phones)"},"422":{"description":"Nenhuma instância WhatsApp conectada"}}}},"/campaigns/{id}/contacts":{"post":{"summary":"Importar contatos com variáveis customizadas","operationId":"importCampaignContacts","description":"Adiciona contatos a uma campanha com variáveis individuais por contato. Use este endpoint quando precisar de variáveis como `{valor}`, `{dia}`, `{vencimento}` que variam por pessoa. As variáveis `{nome}` e `{empresa}` são preenchidas automaticamente do cadastro do contato se não forem fornecidas.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","maxItems":10000,"items":{"type":"object","required":["phone"],"properties":{"phone":{"type":"string","example":"5511999999999","description":"Telefone E.164 sem +"},"variables":{"type":"object","description":"Variáveis de personalização para este contato","example":{"nome":"João Silva","empresa":"ACME","valor":"R$ 350,00","dia":"15/06","vencimento":"2026-06-15"},"additionalProperties":{"type":"string"}}}},"example":[{"phone":"5511999999999","variables":{"nome":"João Silva","valor":"R$ 350,00","dia":"15/06"}},{"phone":"5521888888888","variables":{"nome":"Maria Souza","valor":"R$ 120,00","dia":"20/06"}}]}}}},"responses":{"200":{"description":"Contatos importados","content":{"application/json":{"schema":{"type":"object","properties":{"added":{"type":"integer"},"skipped":{"type":"integer","description":"Inválidos ou com erro"}}}}}},"404":{"description":"Campanha não encontrada"}}}},"/fiscal/empresas":{"get":{"summary":"API Fiscal: emitentes ativos (CNPJ para cnpj_emitente)","operationId":"fiscalEmpresas","responses":{"200":{"description":"Lista de emitentes"}}}},"/fiscal/uso":{"get":{"summary":"API Fiscal: documentos autorizados em produção no mês","operationId":"fiscalUso","parameters":[{"name":"mes","in":"query","schema":{"type":"string","example":"2026-10"}}],"responses":{"200":{"description":"{ mes, por_tipo: { nfe, nfce, nfse }, total }"}}}},"/fiscal/{tipo}":{"post":{"summary":"API Fiscal: emitir NF-e, NFC-e ou NFS-e nacional","description":"Emite na hora (assina, transmite e devolve a nota). `ref` é o identificador do documento no seu sistema: a mesma ref nunca vira duas notas — reenviar uma ref autorizada devolve a nota existente (200); ref rejeitada pode ser corrigida e reenviada. `cnpj_emitente` escolhe o emitente (sem ele, o padrão). Status: autorizado, contingencia, rejeitado, erro, indefinido (sem resposta do emissor: confira antes de usar outra ref), cancelado. Webhooks: fiscal.autorizado, fiscal.rejeitado, fiscal.cancelado.","operationId":"fiscalEmitir","parameters":[{"name":"tipo","in":"path","required":true,"schema":{"type":"string","enum":["nfe","nfce","nfse"]}},{"name":"ref","in":"query","required":true,"schema":{"type":"string","maxLength":64,"example":"PEDIDO-1234"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cnpj_emitente":{"type":"string","example":"12345678000190"},"itens":{"type":"array","description":"NF-e/NFC-e: codigo, descricao, ncm, cfop, unidade, quantidade, valor_unitario, valor_total, tributação (icms_csosn/icms_cst, aliquotas…) e IBS/CBS (ibscbs_cst, c_class_trib)","items":{"type":"object"}},"destinatario":{"type":"object","description":"NF-e: cpf/cnpj, nome, inscricao_estadual, endereco { logradouro, numero, bairro, municipio, cod_municipio, uf, cep }"},"pagamentos":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"A ref já tinha nota: devolve a existente"},"201":{"description":"Nota emitida (status autorizado ou contingencia)"},"202":{"description":"Sem resposta do emissor (status indefinido)"},"409":{"description":"A ref ainda está sendo emitida"},"422":{"description":"Rejeitada pela SEFAZ/prefeitura ou dados inválidos (mensagem explica)"}}}},"/fiscal/{tipo}/{ref}":{"get":{"summary":"API Fiscal: consultar pela ref","operationId":"fiscalConsultar","parameters":[{"name":"tipo","in":"path","required":true,"schema":{"type":"string","enum":["nfe","nfce","nfse"]}},{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Documento"},"404":{"description":"Ref não encontrada"}}},"delete":{"summary":"API Fiscal: cancelar","operationId":"fiscalCancelar","parameters":[{"name":"tipo","in":"path","required":true,"schema":{"type":"string","enum":["nfe","nfce","nfse"]}},{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["justificativa"],"properties":{"justificativa":{"type":"string","minLength":15,"maxLength":255}}}}}},"responses":{"200":{"description":"Cancelada"},"409":{"description":"Não está autorizada"},"422":{"description":"SEFAZ não cancelou"}}}},"/fiscal/{tipo}/{ref}/pdf":{"get":{"summary":"API Fiscal: PDF (DANFE, DANFE NFC-e ou DANFSE)","operationId":"fiscalPdf","parameters":[{"name":"tipo","in":"path","required":true,"schema":{"type":"string","enum":["nfe","nfce","nfse"]}},{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"application/pdf"}}}},"/fiscal/nfe/{ref}/xml":{"get":{"summary":"API Fiscal: XML da NF-e autorizada","operationId":"fiscalXml","parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"application/xml"}}}},"/webhooks":{"get":{"summary":"Listar webhooks registrados","operationId":"listWebhooks","responses":{"200":{"description":"Lista de webhooks","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}}}}}}}}}},"post":{"summary":"Registrar webhook","operationId":"createWebhook","description":"Registra uma URL para receber notificações de eventos. O AutoZap envia um POST com `Content-Type: application/json` e, se `secret` for informado, inclui o header `X-AutoZap-Signature: sha256=HMAC(secret, body)` para validação. Eventos suportados: `message.sent`, `message.failed`, `campaign.completed`, `proposal.accepted`, `deal.updated`, `fiscal.autorizado`, `fiscal.rejeitado`, `fiscal.cancelado`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","example":"https://meu-sistema.com/autozap-webhook"},"events":{"type":"array","items":{"type":"string","enum":["message.sent","message.failed","campaign.completed","proposal.accepted","deal.updated","fiscal.autorizado","fiscal.rejeitado","fiscal.cancelado"]},"example":["message.sent","message.failed","campaign.completed"]},"secret":{"type":"string","example":"meu_segredo_hmac","description":"Chave para validar assinatura HMAC-SHA256. Opcional mas recomendado."}}}}}},"responses":{"201":{"description":"Webhook criado","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"url ou events ausente, ou evento inválido"}}}},"/webhooks/{id}":{"delete":{"summary":"Remover webhook","operationId":"deleteWebhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Webhook removido"},"404":{"description":"Webhook não encontrado"}}}},"/instances":{"get":{"summary":"Listar instâncias WhatsApp","operationId":"listInstances","description":"Retorna todas as instâncias WhatsApp do tenant com status de conexão. Use o `id` como `instance_id` nos endpoints de campanha e envio de mensagem.","responses":{"200":{"description":"Lista de instâncias","content":{"application/json":{"schema":{"type":"object","properties":{"instances":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"UUID da instância — use como instance_id"},"name":{"type":"string","example":"Cobrança Principal"},"instance_name":{"type":"string","example":"cobranca-01"},"phone":{"type":"string","example":"5511999999999"},"status":{"type":"string","enum":["connected","disconnected","banned"]},"connected":{"type":"boolean","description":"true quando status é connected ou open"}}}}}}}}}}}},"/teams":{"get":{"summary":"Listar equipes e seus números de disparo","operationId":"listTeams","description":"Retorna as equipes do tenant com a contagem de números WhatsApp vinculados (total e conectados). Use o `id` ou o `name` como valor de `team` ao criar campanhas (/campaigns/create) ou disparar em lote (/messages/batch) — a campanha sairá em rodízio por TODOS os chips conectados da equipe.","responses":{"200":{"description":"Lista de equipes","content":{"application/json":{"schema":{"type":"object","properties":{"teams":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Gazin"},"color":{"type":"string","example":"#6366f1"},"numbers_total":{"type":"integer","description":"Total de números (chips Evolution) vinculados à equipe"},"numbers_connected":{"type":"integer","description":"Números da equipe atualmente conectados — os que serão usados no disparo"}}}}}}}}}}}},"/campaigns/{id}/stats":{"get":{"summary":"Estatísticas de uma campanha","operationId":"getCampaignStats","description":"Retorna contadores de mensagens por status para uma campanha criada via API.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Estatísticas da campanha","content":{"application/json":{"schema":{"type":"object","properties":{"campaign":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["active","scheduled","paused","completed"]},"campaign_type":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"stats":{"type":"object","properties":{"total":{"type":"integer","description":"Total de contatos na campanha"},"pending":{"type":"integer","description":"Aguardando envio"},"sent":{"type":"integer","description":"Enviadas com sucesso"},"error":{"type":"integer","description":"Falha no envio"},"skipped":{"type":"integer","description":"Puladas (número inválido, bloqueado, etc.)"},"delivered_pct":{"type":"integer","description":"Percentual de entrega (sent/total × 100)"}}}}}}}},"404":{"description":"Campanha não encontrada"}}}},"/deals/{id}":{"patch":{"summary":"Atualizar deal (status, stage, valor)","operationId":"updateDeal","description":"Atualiza campos de um deal. Use `outcome: \"won\"` ou `outcome: \"lost\"` para fechar o deal automaticamente no stage correto do pipeline, sem precisar saber o `stage_id`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"outcome":{"type":"string","enum":["won","lost"],"description":"Fecha o deal no stage de ganho ou perda do pipeline. Sobrepõe stage_id."},"stage_id":{"type":"string","format":"uuid","description":"Move para um stage específico (alternativa ao outcome)"},"title":{"type":"string"},"value":{"type":"number","example":1500},"notes":{"type":"string"},"expected_close":{"type":"string","format":"date"}}}}}},"responses":{"200":{"description":"Deal atualizado","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"value":{"type":"number"},"closed_at":{"type":"string","format":"date-time","description":"Preenchido quando outcome=won/lost"}}}}}},"400":{"description":"Nada para atualizar"},"404":{"description":"Deal não encontrado"}}}},"/deals":{"get":{"summary":"Consultar deals por telefone","operationId":"listDeals","description":"Retorna os deals (negócios) vinculados a um contato pelo telefone. O `id` retornado pode ser usado como `deal_id` ao criar uma proposta.","parameters":[{"name":"phone","in":"query","required":true,"schema":{"type":"string"},"description":"Telefone E.164 sem + (ex: 5511999999999)"}],"responses":{"200":{"description":"Lista de deals","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"deals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Use como deal_id ao criar proposta"},"title":{"type":"string","example":"Negociação Contrato #1234"},"value":{"type":"number","example":1500},"stage_name":{"type":"string","example":"Negociação"},"contact_name":{"type":"string"},"contact_phone":{"type":"string"},"expected_close":{"type":"string","format":"date"},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"400":{"description":"phone ausente ou inválido"}}},"post":{"summary":"Criar deal vinculado a um contato","operationId":"createDeal","description":"Cria um deal no pipeline. Se `phone` for informado, busca ou cria o contato automaticamente. Se `stage_id` for omitido, usa o primeiro stage do primeiro pipeline configurado no tenant.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","example":"Negociação Contrato #1234"},"phone":{"type":"string","example":"5511999999999","description":"Telefone do contato (E.164 sem +). Busca ou cria o contato automaticamente."},"value":{"type":"number","example":1500},"notes":{"type":"string","example":"Cliente com dívida vencida há 60 dias"},"expected_close":{"type":"string","format":"date","example":"2026-06-30"},"stage_id":{"type":"string","format":"uuid","description":"UUID do stage do pipeline (opcional — usa o primeiro stage se omitido)"}}}}}},"responses":{"201":{"description":"Deal criado","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Use como deal_id ao criar proposta"},"title":{"type":"string"},"value":{"type":"number"}}}}}},"400":{"description":"title ausente"},"422":{"description":"Nenhum pipeline configurado no tenant"}}}},"/templates":{"get":{"summary":"Listar templates de mensagem","operationId":"listTemplates","description":"Retorna os templates cadastrados no tenant. Use o campo `name` como valor de `template` no endpoint /messages/batch.","responses":{"200":{"description":"Lista de templates","content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"004_preventivo_parc_vence_hj","description":"Chave única para referenciar no batch"},"category":{"type":"string","example":"billing"},"content":{"type":"string","example":"Olá {dev.nome}, sua parcela do {var1} vence hoje!"}}}}}}}}}}},"post":{"summary":"Criar ou atualizar template de mensagem","operationId":"upsertTemplate","description":"Cria um template ou atualiza o existente de mesmo `name` (upsert por nome — idempotente). O `content` aceita variáveis no formato `{chave}` (ex: `{dev.prinome}`, `{prop1}`), preenchidas pelo `vars` em /messages/batch.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","content"],"properties":{"name":{"type":"string","example":"004_preventivo_parc_vence_hj","description":"Nome único do template (a chave usada no campo template do batch)"},"content":{"type":"string","example":"Olá {dev.prinome}, {prop1}","description":"Texto do template com variáveis no formato {chave}"},"category":{"type":"string","example":"billing","description":"Categoria livre (opcional — default custom)"}}}}}},"responses":{"200":{"description":"Template existente atualizado (mesmo name)"},"201":{"description":"Template criado","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"category":{"type":"string"},"content":{"type":"string"}}}}}},"400":{"description":"name ou content ausente"}}}},"/messages/batch":{"post":{"summary":"Disparo em lote com template e rastreamento por ID externo","operationId":"sendBatch","description":"Envia múltiplas mensagens em um único request. Aceita o formato `{ sendWhastAppMessageRequest: [...] }` ou diretamente o array. Para cada item: se `template` for informado, busca o texto no cadastro de templates; se `message` for informado, usa inline (sobrepõe o template). Os campos `vars` (array `[{type, key, value}]`) são convertidos automaticamente para variáveis de personalização. O `id` de cada item é preservado na resposta para rastreamento. Use `campaign` para nomear a campanha onde as mensagens serão agrupadas e `from` para escolher o número de origem (string = um número; array = rodízio entre vários números/chips). Use `media_url` para anexar um arquivo individual por destinatário (ex: link de PDF), com o texto como legenda. O campo `namespace` (WhatsApp oficial) não é utilizado e pode ser omitido. Quando nem `team` nem `from`/`instance_id` forem informados (no lote ou no item), tenta inferir a equipe automaticamente pelo nome da campanha (ex: campanha \"GAZIN_JUNHO\" casa com a equipe \"Gazin\" cadastrada no tenant) antes de cair na 1ª instância conectada do tenant.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"sendWhastAppMessageRequest":{"type":"array","items":{"$ref":"#/components/schemas/BatchItem"}},"team":{"type":"string","example":"Gazin","description":"Equipe de disparo padrão do lote (nome ou UUID, obtido em GET /teams). Os itens sem `from`/`team` próprios saem em rodízio pelos chips conectados da equipe."},"mask_sensitive_data":{"type":"boolean","default":false,"description":"Quando true, mascara CPF/CNPJ formatados no texto enviado (ex: 061.***.***-01), mantendo só início e fim."}}},{"type":"array","items":{"$ref":"#/components/schemas/BatchItem"}}]}}}},"responses":{"200":{"description":"Resultado por item","content":{"application/json":{"schema":{"type":"object","properties":{"queued":{"type":"integer","description":"Mensagens enfileiradas com sucesso"},"skipped":{"type":"integer","description":"Itens com erro ou inválidos"},"results":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string","example":"213873838","description":"O mesmo id enviado no item"},"phone":{"type":"string"},"status":{"type":"string","enum":["queued","skipped","error"]},"message_id":{"type":"string","format":"uuid","description":"ID interno AutoZap (presente quando queued)"},"error":{"type":"string","description":"Motivo do erro (presente quando skipped/error)"}}}}}}}}},"400":{"description":"Body inválido"},"422":{"description":"Nenhuma instância WhatsApp conectada"}}}},"/personas":{"get":{"summary":"Listar personas de IA","operationId":"listPersonas","description":"Retorna as personas de IA configuradas no tenant. Use o `id` retornado como `ai_persona_id` ao criar campanhas de IA.","responses":{"200":{"description":"Lista de personas","content":{"application/json":{"schema":{"type":"object","properties":{"personas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Use este ID como ai_persona_id na campanha"},"name":{"type":"string","example":"Valentina"},"role":{"type":"string","example":"Consultora de vendas"},"objective":{"type":"string","enum":["sell","qualify","schedule","inform"]},"objective_label":{"type":"string","example":"Vender plano Pro"},"max_turns":{"type":"integer","example":10,"description":"Máximo de trocas de mensagem por contato"},"fallback_action":{"type":"string","enum":["transfer","close"],"description":"O que fazer quando max_turns é atingido"}}}}}}}}}}}},"/proposals":{"post":{"summary":"Criar proposta de acordo","operationId":"createProposal","description":"Cria uma proposta vinculada a um deal. Use `POST /deals` para criar o deal primeiro se necessário.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["deal_id","title"],"properties":{"deal_id":{"type":"string","format":"uuid","description":"ID do deal (obtido em GET /deals ou POST /deals)"},"title":{"type":"string","example":"Proposta de Acordo — Parcela em atraso"},"valid_until":{"type":"string","format":"date","example":"2026-06-30","description":"Data de validade da proposta"},"notes":{"type":"string","example":"Desconto de 10% se pago até o vencimento"},"items":{"type":"array","description":"Itens da proposta","items":{"type":"object","properties":{"name":{"type":"string","example":"Parcela em atraso"},"qty":{"type":"number","example":1},"unit_price":{"type":"number","example":350},"discount_pct":{"type":"number","example":10,"description":"Desconto em %"}}}}}}}}},"responses":{"201":{"description":"Proposta criada","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"status":{"type":"string","enum":["draft","sent","accepted","rejected","expired"]},"token":{"type":"string","description":"Token de 32 chars para compartilhar com o cliente via link"},"valid_until":{"type":"string","format":"date"},"created_at":{"type":"string","format":"date-time"}}}}}},"400":{"description":"deal_id ou title ausente"},"404":{"description":"Deal não encontrado"}}},"get":{"summary":"Consultar propostas por telefone","operationId":"listProposals","description":"Retorna todas as propostas de acordo associadas a um contato pelo telefone.","parameters":[{"name":"phone","in":"query","required":true,"schema":{"type":"string"},"description":"Telefone E.164 sem + (ex: 5511999999999)"}],"responses":{"200":{"description":"Lista de propostas","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"proposals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string","example":"Proposta de Acordo — Contrato #1234"},"status":{"type":"string","enum":["draft","sent","accepted","rejected","expired"]},"valid_until":{"type":"string","format":"date","example":"2026-06-30"},"items":{"type":"array","description":"Itens da proposta (nome, qty, unit_price, discount_pct)"},"token":{"type":"string","description":"Token público de 32 chars para compartilhar com o cliente"},"sent_at":{"type":"string","format":"date-time"},"accepted_at":{"type":"string","format":"date-time"},"deal_title":{"type":"string"},"deal_value":{"type":"number"},"contact_name":{"type":"string"},"contact_phone":{"type":"string"}}}}}}}}},"400":{"description":"phone ausente ou inválido"}}}},"/proposals/{token}":{"get":{"summary":"Consultar proposta por token","operationId":"getProposal","description":"Retorna uma proposta pelo token de 32 caracteres. Ideal para o cliente final confirmar o status de um acordo após receber o link via WhatsApp.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","example":"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"},"description":"Token único de 32 caracteres da proposta"}],"responses":{"200":{"description":"Dados completos da proposta (mesmos campos de /proposals)"},"404":{"description":"Proposta não encontrada ou não pertence a este tenant"}}}},"/products/bulk":{"post":{"summary":"Cadastro/atualização de produtos e estoque em massa","operationId":"bulkUpsertProducts","description":"Cria ou atualiza produtos em lote, incluindo NCM/CEST, código de barras e estoque. O casamento com produtos existentes é feito por `barcode` (quando informado) ou por `name` (case-insensitive). Quando `stock_quantity` é informado para um produto existente, gera uma movimentação de estoque do tipo `adjustment` (define a quantidade absoluta); para produto novo, gera uma movimentação `in`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"products":{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/ProductBulkItem"}}}},{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/ProductBulkItem"}}]}}}},"responses":{"200":{"description":"Resultado por item","content":{"application/json":{"schema":{"type":"object","properties":{"created":{"type":"integer","description":"Produtos novos criados"},"updated":{"type":"integer","description":"Produtos existentes atualizados"},"skipped":{"type":"integer","description":"Itens com erro"},"results":{"type":"array","items":{"type":"object","properties":{"external_id":{"type":"string","example":"SKU-00123","description":"O mesmo id enviado no item"},"barcode":{"type":"string"},"product_id":{"type":"integer","description":"ID interno AutoZap do produto"},"status":{"type":"string","enum":["created","updated","error"]},"error":{"type":"string","description":"Motivo do erro (presente quando status=error)"}}}}}}}}},"400":{"description":"Body inválido"}}}},"/campaigns/{id}/trigger":{"post":{"summary":"Adicionar contatos a campanha existente","operationId":"triggerCampaign","description":"Adiciona novos contatos a uma campanha já existente. Útil para acrescentar números sem recriar a campanha. O campo `message` substitui o template original apenas para os contatos adicionados nesta chamada.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phones"],"properties":{"phones":{"type":"array","items":{"type":"string"},"example":["5511999999999","5521888888888"],"description":"Telefones a adicionar à campanha (E.164 sem +)"},"message":{"type":"string","description":"Sobrescreve o template da campanha (opcional). Suporta as mesmas variáveis: {nome}, {empresa}, {numero}, {data}."}}}}}},"responses":{"200":{"description":"Contatos adicionados","content":{"application/json":{"schema":{"type":"object","properties":{"added":{"type":"integer"}}}}}},"404":{"description":"Campanha não encontrada"}}}}}}