{"info":{"name":"Prosalab API v1","description":"Coleção da API pública v1 do Prosalab — contatos, mensagens, campanhas, listas, campos personalizados, importação em massa, conexões e etapas de pipeline. Configure as variáveis \"base_url\" e \"api_key\" da coleção antes de usar.","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json"},"variable":[{"key":"base_url","value":"https://www.prosalab.com","description":"URL base da API."},{"key":"api_key","value":"","description":"Sua chave de API — crie uma em Configurações > API no painel do Prosalab."}],"item":[{"name":"Contatos","item":[{"name":"GET /api/v1/contacts","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/contacts","host":["{{base_url}}"],"path":["api","v1","contacts"]},"description":"Lista contatos com dados de CRM. Filtros: ?search=&source_platform=&inbox_status=open&pipeline_stage_id=&crm_status=&is_ai_enabled=true&page=1&limit=50. Para incluir o resumo de cliques em links curtos, use &include=link_clicks. Para exportar a base inteira, use ?order_by=created_at e siga pagination.next_cursor no parâmetro &cursor=.\n\ninclude=link_clicks é opcional e acrescenta `link_clicks: { total, last_at }` em cada contato. A métrica conta apenas cliques humanos; preview automático do WhatsApp e bots ficam fora.\n\n`inbox_status=resolved` é a conversa encerrada; ela volta para `open` quando o cliente manda mensagem nova, quando um atendente assume ou quando sai uma campanha ou template para ele. Uma resposta nova do cliente após o encerramento volta sem responsável e com a IA da conversa desligada. Cada contato traz `last_closed_at` (quando a conversa foi encerrada pela última vez, ou null se nunca foi) e `last_close_reason_id` (o motivo escolhido, ou null). Os dois são somente leitura e continuam preenchidos depois que a conversa reabre.\n\nA listagem traz os mesmos campos de CRM do caminho interno, incluindo `custom_fields`, observação, CPF, RG, endereço, aniversário, estado, gênero, estado civil, profissão, cargo e organização. Na listagem, `custom_fields` mantém as chaves gravadas no contato (slugs).\n\n`pipeline_stage_id` (e `pipeline_stage`) é a etapa da negociação em foco do contato: entre as negociações com etapa, a ativa que entrou por último na etapa ou, sem nenhuma ativa, a fechada que entrou por último. Contato com várias negociações aparece numa etapa só, e o filtro `pipeline_stage_id=` compara com essa mesma etapa. Contato sem negociação com etapa vem com `pipeline_stage_id: null`.\n\nO endpoint retorna JSON com `Content-Type: application/json` e autentica com `Authorization: Bearer <token>`.\n\nOrdenação padrão (order_by=last_message_at): `last_message_at` decrescente, contatos sem mensagem por último, com desempate por `id` — a ordem entre páginas é estável. Como cada mensagem nova move o contato para o topo, a paginação por `page` pode repetir ou pular um contato que recebeu mensagem durante a leitura.\n\nExportação completa (order_by=created_at): leitura sequencial por cursor, em `created_at` crescente com desempate por `id`, que não repete nem pula contatos. A primeira chamada vai sem `cursor`; cada resposta traz `pagination: { limit, total, next_cursor }`, e a próxima página se pede com `&cursor=<next_cursor>`. `next_cursor: null` indica o fim. `total` é a contagem com os mesmos filtros. O parâmetro `page` não é aceito nesse modo, e todos os filtros e o `include=link_clicks` continuam valendo.\n\nRate limit na exportação: o padrão é de 60 requisições por minuto por chave (configurável por chave). Com limit=100, cada 6.000 contatos consomem 1 minuto de cota. Ao receber 429, aguarde e repita a chamada com o mesmo `next_cursor` — o cursor não expira."}},{"name":"GET /api/v1/contacts/{id}","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/contacts/:id","host":["{{base_url}}"],"path":["api","v1","contacts",":id"],"variable":[{"key":"id","value":""}]},"description":"Detalhe do contato com etiquetas, etapa de pipeline, responsável, campos personalizados, conexões e — para chaves com o escopo lead_memory:read — a memória do lead inferida pela IA.\n\n`inbox_status=resolved` é a conversa encerrada. `last_closed_at` e `last_close_reason_id` são somente leitura: um PATCH que os envie é ignorado, sem erro.\n\n`pipeline_stage_id` (e `pipeline_stage`) é a etapa da negociação em foco do contato, com a mesma regra da listagem.\n\ncustom_fields tem a MESMA forma na leitura e na escrita: chave = ID do campo (UUID), valor = STRING. O bloco desta resposta pode ser reenviado como está em POST e PATCH. Só aparecem campos com definição ATIVA e valor preenchido; contato sem nenhum devolve {} (nunca null).\n\nO rótulo e o tipo de cada campo NÃO viajam aqui — eles pertencem à definição e vivem em GET /api/v1/custom-fields, que é a fonte única. Para montar uma tela, cruze pelo id.\n\nlead_memory é INFERIDO pela IA a partir da conversa, não declarado pelo contato. Trate como hipótese de trabalho: summary, sentiment, intent, consciousness_level e bant são julgamento do modelo e podem estar errados. key_facts, products_interested e objections são listas cumulativas, com teto de 20, 10 e 10 itens.\n\nO campo só aparece quando a chave tem o escopo lead_memory:read. Sem o escopo a resposta é idêntica à anterior e a chave lead_memory NÃO vem no JSON — não é 403. Contato que ainda não tem memória devolve lead_memory: null. Ausência da chave significa \"falta escopo\"; null significa \"sem memória\".\n\nbant.authority tem DOIS formatos por herança: em registros novos é um código estável (ex.: decisor-com-consulta) e o texto original vem em bant.authority_raw; em registros antigos authority é o próprio texto livre e authority_raw não existe. Para exibir ao usuário leia authority_raw ?? authority; para agrupar/contar use authority apenas quando authority_raw estiver presente."}},{"name":"POST /api/v1/contacts","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/contacts","host":["{{base_url}}"],"path":["api","v1","contacts"]},"description":"Cria um contato. Telefone duplicado retorna 409 com o contact_id do existente.\n\ncustom_fields é keyed pelo ID do campo (UUID retornado por GET /api/v1/custom-fields), com valores sempre STRING (máx. 500 caracteres) e sem limite de quantidade de chaves. ID inexistente/inativo/de outra empresa é rejeitado com 422, com a lista dos inválidos em details.custom_fields e o caminho de conserto em details.hint.\n\nRound-trip suportado: o custom_fields devolvido por GET /api/v1/contacts/{id} tem exatamente esta forma e pode ser reenviado aqui sem conversão.\n\nCampos nativos de identidade e endereço vão no top-level (cpf, rg, genero, estado_civil, profissao, city, state, postal_code) — não em custom_fields. cpf e postal_code são normalizados para dígitos pelo servidor.\n\ncnpj (opcional, normalizado para dígitos) cria ou associa uma empresa-cliente (organization) e busca a razão social automaticamente em segundo plano.\n\nlist_ids é validado ANTES da criação: uma list_id de outra empresa ou inativa retorna 404 e o contato NÃO é criado. Só a falha de infraestrutura na associação PÓS-criação vira o campo warnings na resposta (o contato existe).\n\nshared_email (somente leitura) aparece quando o e-mail enviado já pertence a outro contato: o telefone manda na identidade, o contato é criado normalmente e o e-mail informado fica guardado em shared_email em vez de email — ele NÃO identifica este contato. Quando isso acontece, warnings traz o aviso \"O e-mail informado já pertence a outro contato; foi guardado em shared_email e não identifica este contato.\" e a criação continua sendo 201, não 409.","body":{"mode":"raw","raw":"{\n  \"phone\": \"5548999990000\",\n  \"name\": \"João Silva\",\n  \"email\": \"joao@exemplo.com.br\",\n  \"birthday\": \"1990-05-12\",\n  \"observation\": \"Veio da feira de negócios\",\n  \"cpf\": \"99001122334\",\n  \"rg\": \"1234567\",\n  \"genero\": \"Masculino\",\n  \"estado_civil\": \"Casado\",\n  \"profissao\": \"Engenheiro\",\n  \"city\": \"Florianópolis\",\n  \"state\": \"SC\",\n  \"postal_code\": \"88010000\",\n  \"cnpj\": \"12345678000190\",\n  \"custom_fields\": {\n    \"<id-do-campo-uuid>\": \"campanha\"\n  },\n  \"list_ids\": [\n    \"<uuid>\"\n  ]\n}","options":{"raw":{"language":"json"}}}}},{"name":"PATCH /api/v1/contacts/{id}","request":{"method":"PATCH","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/contacts/:id","host":["{{base_url}}"],"path":["api","v1","contacts",":id"],"variable":[{"key":"id","value":""}]},"description":"Atualiza um contato (merge parcial — só os campos enviados; telefone é imutável).\n\ncustom_fields (keyed pelo ID do campo, valores string) faz merge campo a campo sobre o valor atual — não substitui o objeto inteiro.\n\nCampos nativos (cpf, rg, genero, estado_civil, profissao, city, state, postal_code) são atualizados individualmente; enviar \"\" limpa o campo.\n\ncnpj só associa uma organization se o contato ainda NÃO tiver uma — não sobrescreve o vínculo existente.\n\nO corpo GRAVÁVEL é: name, email, birthday, observation, city, state, postal_code, cpf, rg, genero, estado_civil, profissao, cnpj, custom_fields, list_ids.\n\nRound-trip suportado: pegue o corpo de GET /api/v1/contacts/{id}, altere o que quiser e reenvie inteiro. Os campos só-leitura da resposta (id, display_name, crm_status, inbox_status, tags, connections, assigned_to, lead_memory, created_at, updated_at) são IGNORADOS em silêncio — são eco da leitura, não intenção de escrita.\n\nphone e identifier continuam imutáveis, mas com a comparação certa: reenviar o valor atual (inclusive reformatado, como \"+55 (11) 99999-9999\") é aceito; enviar um número DIFERENTE retorna 422 com details.fields. Para trocar o número, crie um novo contato.\n\nChave desconhecida (um \"nome\" no lugar de \"name\") continua retornando 422 — o descarte vale só para a lista de só-leitura acima, para que erro de digitação não passe despercebido.","body":{"mode":"raw","raw":"{\n  \"name\": \"João da Silva\",\n  \"email\": \"joao.silva@exemplo.com.br\",\n  \"profissao\": \"Arquiteto\",\n  \"custom_fields\": {\n    \"<id-do-campo-uuid>\": \"indicação\"\n  }\n}","options":{"raw":{"language":"json"}}}}},{"name":"DELETE /api/v1/contacts/{id}","request":{"method":"DELETE","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/contacts/:id","host":["{{base_url}}"],"path":["api","v1","contacts",":id"],"variable":[{"key":"id","value":""}]},"description":"Remove o contato e purga seus documentos anexados (LGPD)."}}]},{"name":"Envio de mensagens","item":[{"name":"POST /api/v1/send/message","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/send/message","host":["{{base_url}}"],"path":["api","v1","send","message"]},"description":"Envia uma mensagem de texto via WhatsApp (UAZAPI ou Meta Cloud).","body":{"mode":"raw","raw":"{\n  \"to\": \"5548999990000\",\n  \"message\": \"Olá! Seu pedido foi confirmado.\",\n  \"connection_id\": \"<opcional — UUID da conexão>\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"POST /api/v1/send/template","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/send/template","host":["{{base_url}}"],"path":["api","v1","send","template"]},"description":"Envia uma mensagem de template/HSM aprovado na Meta Cloud. Template com variável (corpo, cabeçalho ou botão de URL) exige o array components — sem ele a Meta recebe o template sem preenchimento.\n\ncomponents é repassado à Meta EXATAMENTE como enviado — esta rota não monta, valida nem completa nenhum componente. Template com variável enviado sem components chega à Meta sem preenchimento; a falha é da Meta, não desta rota.\n\nBotão de URL dinâmica: a Meta aceita UMA variável por botão, e apenas no FIM de uma URL já aprovada — o valor enviado em parameters[0].text é concatenado à base aprovada. index é a posição do botão no template (\"0\" para o primeiro), enviado como STRING. URL fora da base aprovada faz a Meta rejeitar o envio inteiro.\n\nNão há endpoint para listar templates aprovados, e template_name/language_code NÃO são validados contra o catálogo antes do envio. Nome inexistente, idioma errado ou template não aprovado só falham na Meta e voltam como 502 PROVIDER_ERROR genérico — confira o nome e o idioma no Gerenciador da Meta antes de disparar.\n\nExige conexão WhatsApp Meta Cloud. Informando connection_id, ele PRECISA ser Meta Cloud; sem ele, a conexão Meta Cloud conectada da empresa é resolvida automaticamente. Conexão de outro tipo também volta como 502, não 400.\n\nA mensagem é gravada com o template_name usado no disparo, o que permite auditar e deduplicar envios por template.","body":{"mode":"raw","raw":"{\n  \"to\": \"5548999990000\",\n  \"template_name\": \"renovacao_proposta\",\n  \"language_code\": \"pt_BR\",\n  \"connection_id\": \"<opcional — UUID da conexão>\",\n  \"components\": [\n    {\n      \"type\": \"body\",\n      \"parameters\": [\n        {\n          \"type\": \"text\",\n          \"text\": \"Maria\"\n        }\n      ]\n    },\n    {\n      \"type\": \"button\",\n      \"sub_type\": \"url\",\n      \"index\": \"0\",\n      \"parameters\": [\n        {\n          \"type\": \"text\",\n          \"text\": \"aB3xK9\"\n        }\n      ]\n    }\n  ]\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Mensagens","item":[{"name":"GET /api/v1/messages","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/messages","host":["{{base_url}}"],"path":["api","v1","messages"]},"description":"Lista mensagens da empresa. Filtros: ?contact_id=&connection_id=&direction=inbound&type=text&status=delivered&from=&to=&page=1&limit=50\n\nDeduplique por `prosa.consumption_id` antes de somar `prosa.cost_brl`: com `scope: \"turn\"` o mesmo débito cobre outros balões da mesma resposta, e somar mensagem a mensagem o conta mais de uma vez. Com `scope: \"message\"` o débito cobre só aquela mensagem.\n\n`prosa: null` significa que não há débito atribuído à mensagem — nunca que ela custou zero. Cai nesse estado a mensagem recebida (inbound não custa), o envio manual do operador, a mensagem anterior à entrada deste campo em produção e o envio de campanha, que debita antes de a mensagem existir e aparece em GET /api/v1/prosas/consumption e no relatório da campanha.\n\n`prosa.prosas` e `prosa.cost_brl` são os valores do débito INTEIRO, não uma fração por balão, e refletem a cobrança atribuída à mensagem. Estornos aparecem apenas no ledger (GET /api/v1/prosas/consumption), como linha de valor negativo.\n\n`meta_template_category` é `null` em toda mensagem que não saiu de um template aprovado — texto livre da IA, envio manual e mensagem recebida.\n\nQuando houver falha, `delivery_error` traz `{ code, description }`: use `code` para automação e `description` para exibir o significado em português. `error_message`/`failure_reason` continuam com o retorno bruto do provedor apenas por compatibilidade."}},{"name":"GET /api/v1/contacts/{id}/messages","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/contacts/:id/messages","host":["{{base_url}}"],"path":["api","v1","contacts",":id","messages"],"variable":[{"key":"id","value":""}]},"description":"Histórico de mensagens de um contato. Filtros: ?from=&to=&direction=&page=1&limit=50\n\nDeduplique por `prosa.consumption_id` antes de somar `prosa.cost_brl`: com `scope: \"turn\"` o mesmo débito cobre outros balões da mesma resposta, e somar mensagem a mensagem o conta mais de uma vez. Com `scope: \"message\"` o débito cobre só aquela mensagem.\n\n`prosa: null` significa que não há débito atribuído à mensagem — nunca que ela custou zero. Cai nesse estado a mensagem recebida (inbound não custa), o envio manual do operador, a mensagem anterior à entrada deste campo em produção e o envio de campanha, que debita antes de a mensagem existir e aparece em GET /api/v1/prosas/consumption e no relatório da campanha.\n\n`prosa.prosas` e `prosa.cost_brl` são os valores do débito INTEIRO, não uma fração por balão, e refletem a cobrança atribuída à mensagem. Estornos aparecem apenas no ledger (GET /api/v1/prosas/consumption), como linha de valor negativo.\n\n`meta_template_category` é `null` em toda mensagem que não saiu de um template aprovado — texto livre da IA, envio manual e mensagem recebida.\n\nQuando houver falha, `delivery_error` traz `{ code, description }`: use `code` para automação e `description` para exibir o significado em português. `error_message`/`failure_reason` continuam com o retorno bruto do provedor apenas por compatibilidade."}}]},{"name":"Listas","item":[{"name":"GET /api/v1/lists","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/lists","host":["{{base_url}}"],"path":["api","v1","lists"]},"description":"Lista as listas ativas da empresa."}},{"name":"POST /api/v1/lists","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/lists","host":["{{base_url}}"],"path":["api","v1","lists"]},"description":"Cria uma lista. Nome duplicado (entre listas ativas) retorna 409; um nome antes removido é reativado.","body":{"mode":"raw","raw":"{\n  \"name\": \"VIP\",\n  \"color\": \"#6366f1\",\n  \"description\": \"Clientes VIP\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"PATCH /api/v1/lists/{id}","request":{"method":"PATCH","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/lists/:id","host":["{{base_url}}"],"path":["api","v1","lists",":id"],"variable":[{"key":"id","value":""}]},"description":"Renomeia ou recolore uma lista.","body":{"mode":"raw","raw":"{\n  \"name\": \"VIP 2026\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"DELETE /api/v1/lists/{id}","request":{"method":"DELETE","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/lists/:id","host":["{{base_url}}"],"path":["api","v1","lists",":id"],"variable":[{"key":"id","value":""}]},"description":"Desativa a lista (soft delete — idempotente)."}},{"name":"POST /api/v1/lists/{id}/contacts","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/lists/:id/contacts","host":["{{base_url}}"],"path":["api","v1","lists",":id","contacts"],"variable":[{"key":"id","value":""}]},"description":"Associa contatos à lista em lote (até 500 por chamada).\n\ncontact_ids que não pertencem à empresa contam como not_found (não é erro fatal).","body":{"mode":"raw","raw":"{\n  \"contact_ids\": [\n    \"<uuid>\",\n    \"<uuid>\"\n  ]\n}","options":{"raw":{"language":"json"}}}}},{"name":"DELETE /api/v1/lists/{id}/contacts","request":{"method":"DELETE","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/lists/:id/contacts","host":["{{base_url}}"],"path":["api","v1","lists",":id","contacts"],"variable":[{"key":"id","value":""}]},"description":"Remove contatos da lista em lote (até 500 por chamada).","body":{"mode":"raw","raw":"{\n  \"contact_ids\": [\n    \"<uuid>\"\n  ]\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Campos personalizados","item":[{"name":"GET /api/v1/custom-fields","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/custom-fields","host":["{{base_url}}"],"path":["api","v1","custom-fields"]},"description":"Lista as definições de campos personalizados ativas."}},{"name":"POST /api/v1/custom-fields","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/custom-fields","host":["{{base_url}}"],"path":["api","v1","custom-fields"]},"description":"Cria uma definição de campo personalizado. O slug é derivado do label pelo servidor.\n\nfield_type aceita: text, number, date, boolean, select, email, phone, url, currency, cpf.\n\nselect_options só é usado quando field_type = select.\n\nNão há limite de quantidade de campos personalizados por empresa.","body":{"mode":"raw","raw":"{\n  \"label\": \"Placa do carro\",\n  \"field_type\": \"text\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"PATCH /api/v1/custom-fields/{id}","request":{"method":"PATCH","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/custom-fields/:id","host":["{{base_url}}"],"path":["api","v1","custom-fields",":id"],"variable":[{"key":"id","value":""}]},"description":"Edita o rótulo, opções ou obrigatoriedade do campo (o slug é imutável).","body":{"mode":"raw","raw":"{\n  \"label\": \"Placa\"\n}","options":{"raw":{"language":"json"}}}}},{"name":"DELETE /api/v1/custom-fields/{id}","request":{"method":"DELETE","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/custom-fields/:id","host":["{{base_url}}"],"path":["api","v1","custom-fields",":id"],"variable":[{"key":"id","value":""}]},"description":"Desativa o campo personalizado (soft delete — idempotente)."}}]},{"name":"Campanhas","item":[{"name":"GET /api/v1/campaigns","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/campaigns","host":["{{base_url}}"],"path":["api","v1","campaigns"]},"description":"Lista as campanhas da empresa. Filtro: ?status=draft,sending"}},{"name":"GET /api/v1/campaigns/{id}","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/campaigns/:id","host":["{{base_url}}"],"path":["api","v1","campaigns",":id"],"variable":[{"key":"id","value":""}]},"description":"Status e métricas de entrega de uma campanha.\n\nAs três taxas vêm da mesma fonte da aba Campanhas de /relatorios, com a campanha inteira (todos os destinatários, sem janela de período): percentual de 0 a 100 com 1 casa decimal, denominador `sent` (destinatários com envio registrado).\n\n`delivered` conta quem tem confirmação de entrega OU de leitura; `read` conta quem tem confirmação de leitura; `replied` conta quem respondeu à campanha.\n\nOs campos `messages_*` e `*_cost_usd` são os contadores gravados na própria campanha e podem divergir das taxas; para números consistentes entre si use `/api/v1/campaigns/{id}/report`.\n\nQuando o cálculo das taxas está indisponível, a campanha vem normalmente e `delivery_rate`, `read_rate` e `reply_rate` vêm `null` — trate `null` como \"sem dado agora\", nunca como zero."}},{"name":"POST /api/v1/campaigns/{id}/start","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/campaigns/:id/start","host":["{{base_url}}"],"path":["api","v1","campaigns",":id","start"],"variable":[{"key":"id","value":""}]},"description":"Inicia uma campanha em rascunho."}},{"name":"POST /api/v1/campaigns/{id}/pause","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/campaigns/:id/pause","host":["{{base_url}}"],"path":["api","v1","campaigns",":id","pause"],"variable":[{"key":"id","value":""}]},"description":"Pausa uma campanha em envio."}},{"name":"POST /api/v1/campaigns/{id}/resume","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/campaigns/:id/resume","host":["{{base_url}}"],"path":["api","v1","campaigns",":id","resume"],"variable":[{"key":"id","value":""}]},"description":"Retoma uma campanha pausada."}},{"name":"GET /api/v1/campaigns/{id}/recipients","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/campaigns/:id/recipients","host":["{{base_url}}"],"path":["api","v1","campaigns",":id","recipients"],"variable":[{"key":"id","value":""}]},"description":"Lista paginada de destinatários. Parâmetros: ?page=1&limit=50&status=sent\n\nQuando houver falha, `delivery_error` traz `{ code, description }`: use `code` para automação e `description` para exibir o significado em português. `error_message`/`failure_reason` continuam com o retorno bruto do provedor apenas por compatibilidade.\n\nOrdenação: `sent_at` decrescente, destinatários ainda não enviados por último, com desempate por `id` — a ordem entre páginas é estável. Durante um disparo em andamento, destinatários recém-enviados sobem para o topo e podem deslocar as páginas seguintes."}},{"name":"POST /api/v1/campaigns/{id}/recipients","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/campaigns/:id/recipients","host":["{{base_url}}"],"path":["api","v1","campaigns",":id","recipients"],"variable":[{"key":"id","value":""}]},"description":"Adiciona destinatários a uma campanha em rascunho/agendada (por contact_ids e/ou por telefone; máx. 100 por chamada).\n\nInforme ao menos um contact_id ou um contato por telefone; o total combinado deve ser ≤ 100.","body":{"mode":"raw","raw":"{\n  \"contact_ids\": [\n    \"<uuid>\"\n  ],\n  \"contacts\": [\n    {\n      \"phone\": \"5548999990000\",\n      \"name\": \"João Silva\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}}}},{"name":"GET /api/v1/campaigns/{id}/report","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/campaigns/:id/report","host":["{{base_url}}"],"path":["api","v1","campaigns",":id","report"],"variable":[{"key":"id","value":""}]},"description":"Relatório consolidado: métricas, taxas e breakdown por status de destinatário.\n\n`metrics` é a campanha inteira, na mesma fonte da aba Campanhas de /relatorios: todos os destinatários, sem janela de período.\n\n`sent` = destinatários com envio registrado; `delivered` = com confirmação de entrega OU de leitura; `read` = com confirmação de leitura; `replied` = que responderam à campanha; `failed` = com status de falha e sem confirmação de entrega nem de leitura.\n\nTaxas (`delivery_rate`, `read_rate`, `reply_rate`) em percentual de 0 a 100 com 1 casa decimal, todas sobre `sent`.\n\n`costs` segue as mesmas regras do bloco `costs` de `/api/v1/reports/campaigns`, sempre em R$ (BRL).\n\n`recipients_by_status` é o estado da fila de envio: contagem exata por status, sempre com os nove status (`pending`, `queued`, `processing`, `sent`, `delivered`, `read`, `failed`, `skipped`, `cancelled`). O status quase nunca avança com o recibo da Meta, por isso `delivered` e `read` aqui não coincidem com `metrics` — para resultado use `metrics`."}},{"name":"POST /api/v1/campaigns/{id}/send","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/campaigns/:id/send","host":["{{base_url}}"],"path":["api","v1","campaigns",":id","send"],"variable":[{"key":"id","value":""}]},"description":"Adiciona contatos (por nome e telefone) e inicia a campanha em um único request.","body":{"mode":"raw","raw":"{\n  \"contacts\": [\n    {\n      \"name\": \"João Silva\",\n      \"phone\": \"5548999990000\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}}}}]},{"name":"Importação em massa","item":[{"name":"POST /api/v1/contacts/bulk","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{api_key}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{base_url}}/api/v1/contacts/bulk","host":["{{base_url}}"],"path":["api","v1","contacts","bulk"]},"description":"Enfileira um lote de importação de contatos. Responde 202 com o job_id para acompanhamento.\n\nTodo lote ganha automaticamente uma etiqueta import_DDMMAAAA_HHhMMmin_api com a data/hora de início da importação.\n\nLimites: até 10.000 contatos por job, corpo máximo de 4MB, no máximo 2 importações ativas por empresa.\n\ncustom_fields é keyed pelo ID do campo (UUID de GET /api/v1/custom-fields), valores sempre string; um ID fora das definições ativas da empresa retorna 422 no enfileiramento (crie os campos antes via POST /api/v1/custom-fields).\n\nCampos nativos (cpf, rg, genero, estado_civil, profissao, city, state, postal_code) vão no top-level de cada contato; cnpj cria/associa a empresa-cliente (organization) com deduplicação por CNPJ dentro do lote.","body":{"mode":"raw","raw":"{\n  \"contacts\": [\n    {\n      \"phone\": \"5548999990000\",\n      \"name\": \"João Silva\",\n      \"email\": \"joao@exemplo.com.br\",\n      \"birthday\": \"1990-05-12\",\n      \"observation\": \"Veio da feira de negócios\",\n      \"cpf\": \"99001122334\",\n      \"genero\": \"Masculino\",\n      \"estado_civil\": \"Casado\",\n      \"city\": \"Florianópolis\",\n      \"state\": \"SC\",\n      \"postal_code\": \"88010000\",\n      \"cnpj\": \"12345678000190\",\n      \"custom_fields\": {\n        \"<id-do-campo-uuid>\": \"campanha\",\n        \"<id-de-outro-campo-uuid>\": \"9\"\n      }\n    }\n  ],\n  \"options\": {\n    \"update_existing_names\": true,\n    \"list_ids\": [\n      \"<uuid>\"\n    ]\n  }\n}","options":{"raw":{"language":"json"}}}}},{"name":"GET /api/v1/contacts/bulk/{jobId}","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/contacts/bulk/:jobId","host":["{{base_url}}"],"path":["api","v1","contacts","bulk",":jobId"],"variable":[{"key":"jobId","value":""}]},"description":"Progresso do job de importação: contadores, percentual e erros por item."}}]},{"name":"Conexões","item":[{"name":"GET /api/v1/connections","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/connections","host":["{{base_url}}"],"path":["api","v1","connections"]},"description":"Lista as conexões da empresa (WhatsApp, Instagram)."}}]},{"name":"Etapas de pipeline","item":[{"name":"GET /api/v1/pipeline-stages","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/pipeline-stages","host":["{{base_url}}"],"path":["api","v1","pipeline-stages"]},"description":"Lista as etapas do pipeline de CRM."}}]},{"name":"Consumo de Prosas","item":[{"name":"GET /api/v1/prosas/consumption","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/prosas/consumption","host":["{{base_url}}"],"path":["api","v1","prosas","consumption"]},"description":"Lista os movimentos de consumo de Prosas. Filtros: ?start_date=&end_date=&category=&source=&contact_id=&page=1&limit=50&currency=brl (e, com `include=meta_lines`, &delivery_status=&meta_page=&meta_limit=). Sem datas, a janela é dos últimos 30 dias. Na página 1 o envelope traz `meta_costs`, o custo Meta estimado do período na moeda de `currency` (`brl` ou `usd`, padrão `brl`). Com `?include=meta_lines` o envelope ganha `meta_lines`: cada envio com custo Meta registrado (template ou resposta de conversa), em grão de linha, com paginação própria (`meta_page`, padrão 1; `meta_limit`, padrão 50, teto 100) — independente de `page`/`limit` — e ordem da cobrança mais recente para a mais antiga (linha `void`, sem data de cobrança, fica no fim). Com `include=meta_lines`, `category` aceita, além do vocabulário de Prosas, as categorias Meta (`marketing`, `utility`, `authentication`, `authentication-international`, `service`, `unknown`); `prospection` e `human` são só de Prosas e devolvem `meta_lines` vazio. Sem `include`, `category` aceita só o vocabulário de Prosas.\n\n`prosas` e `cost_brl` são ASSINADOS: uma linha negativa é o estorno de uma cobrança anterior. Some os valores como vêm — filtrar o negativo superestima o custo.\n\nEm `meta_costs`, `messages` (em `totals`, `by_category` e `by_day`) e `costs.sent` contam LINHAS DE CUSTO do período, inclusive as `not_applicable` e `void` — não são o número de envios.\n\n`coverage` diz quais cobranças estão desligadas na empresa. Com `campaign_billing_enabled: false` não existe linha de campanha; com `messages_billing_enabled: false` não existe linha nenhuma. Leia esse bloco antes de concluir que um período não teve consumo.\n\nSem `start_date`, a janela recua 30 dias a partir de `end_date` (ou de agora). A tabela inteira nunca é varrida numa só chamada.\n\n`limit` vai até 100 e a ordenação é por data decrescente. O rate limit é de 60 requisições por minuto por chave: use `pagination.next_page` para retomar a paginação de onde parou depois de um 429.\n\n`contact` vem `null` quando o débito não tem lead — prospecção, enriquecimento, contato excluído, ou linha anterior à atribuição por lead.\n\n`meta_costs` é do PERÍODO, não da página: vem só na página 1 (sem `page` ou `page=1`); nas demais páginas vem `null`, para não ser contado de novo a cada página.\n\nPeríodo de `meta_costs`: data sem hora (`AAAA-MM-DD`) é lida no fuso da empresa — `start_date` desde 00:00 local e `end_date` até o fim do dia local; data com hora (ISO 8601 completo) é usada como veio, com `end_date` exclusivo. `meta_costs.period` traz o intervalo efetivo em UTC. As linhas de Prosas mantêm a leitura atual das datas, então os dois recortes podem diferir nas bordas do dia.\n\n`meta_costs` é custo Meta ESTIMADO pela tarifa base da Meta (em R$, US$ × PTAX de venda do dia), de natureza diferente das Prosas: nunca some os dois. Cada linha de custo entra pela data de cobrança — recorte diferente do relatório de campanhas, que conta a mensagem pelo horário de envio.\n\n`costs.amount` fica `null` enquanto houver linha `pending` na moeda (a PTAX do dia sai por volta das 13h de Brasília, em dias úteis); `costs.known_amount` é sempre o parcial já conhecido.\n\nSe o resumo de custo Meta não puder ser lido, a resposta continua 200 com `meta_costs: null` e `degraded: [\"meta_costs\"]`; as linhas de Prosas e `coverage` não são afetadas.\n\n`meta_lines` só aparece com `?include=meta_lines`. É dinheiro de natureza DIFERENTE de `data`: `meta_lines[].cost` é tarifa Meta estimada, `data[].cost_brl` é crédito interno de Prosa — nunca some os dois. Sem `?include=meta_lines` a resposta é idêntica à de hoje.\n\n`meta_lines` tem paginação PRÓPRIA (`meta_page`, `meta_limit`, teto 100), independente de `page`/`limit` das linhas de Prosa, e do mesmo período de `meta_costs`.\n\n`meta_lines[].status` é o status na moeda pedida — em R$ a linha pode seguir `pending` até a PTAX do dia ser publicada, com o valor em US$ já resolvido. `meta_lines[].cost` fica `null` quando esse status não carrega valor monetário (pending, free, not_billed, void, not_applicable). `meta_lines[].contact` vem `null` em custo órfão (sem mensagem associada) — a linha ainda traz `template_name`.\n\n`category` em `meta_lines` aceita as categorias Meta (marketing, utility, authentication, authentication-international, service, unknown); `prospection` e `human` são só de Prosas e devolvem `meta_lines.data: []`.\n\n`meta_lines[].origin` é `campaign` (envio de campanha), `template` (template fora de campanha, como a régua — reconhecido pelo nome, pelo tipo da mensagem ou pela categoria marketing/utility/authentication) ou `conversation` (resposta de conversa, sem template — normalmente grátis na janela de 24 h).\n\n`meta_lines[].delivery_status` é a situação do envio: `sent` (enviado, sem confirmação de entrega), `delivered` (entregue), `read` (lido) ou `failed` (falhou — a Meta não cobra); `null` em custo órfão. Outro valor de status, raro, sai como está e não é filtrável. Para filtrar use `delivery_status=` com um ou mais valores separados por vírgula — \"entregue\" é `delivery_status=delivered,read`, porque lido também foi entregue. Sem `include=meta_lines` o parâmetro é ignorado.\n\nSe a leitura de `meta_lines` falhar, a resposta continua 200 com `meta_lines: null` e `degraded` ganha `\"meta_lines\"`; os demais blocos não são afetados."}}]},{"name":"Relatórios","item":[{"name":"GET /api/v1/reports/campaigns","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{api_key}}"}],"url":{"raw":"{{base_url}}/api/v1/reports/campaigns","host":["{{base_url}}"],"path":["api","v1","reports","campaigns"]},"description":"Relatório de campanhas e fluxos de template, com custo por categoria da Meta (marketing, utility, authentication, authentication-international, service). Filtros: ?start_date=&end_date=&connection_id=&campaign_id=&template_name=&source=&campaign_status=&category=&currency=. O período também aceita `preset=today|last7|last30` ou `preset=custom&from=&to=` como alternativa a start_date/end_date; qualquer um dos dois formatos resolve para o mesmo `period` na resposta. `currency` aceita brl (padrão) ou usd e define a moeda de todos os custos. Use &include=filters para receber também as opções válidas de cada filtro — qualquer outro valor de `include` é 422, nunca ignorado em silêncio. `totals.campaigns` conta só campanhas com envio no período; os fluxos de template com envio ficam em `totals.template_sources`. O período segue o mesmo teto das demais abas de relatório: no máximo 1830 dias; com `g=hour|day|week` vale o máximo da visão pedida (2, 92 e 366 dias). `daily` traz um item por dia do período até hoje (no fuso da empresa), com zeros nos dias sem envio, e fica vazio quando não houve nenhum envio.\n\nCusto só nasce quando a Meta confirma ENTREGA (`delivered` ou `read`) — uma mensagem `sent` sem confirmação fica `pending`, nunca zero.\n\n`amount` só tem valor com `pending: 0` no mesmo bloco; havendo pendência, some `null` e `known_amount` traz o parcial já resolvido. Nunca some `known_amount` como se fosse o total.\n\nConexões `uazapi` não são Meta Cloud API: o custo dessas linhas é `not_applicable`, e a categoria dessas linhas não é cobrada.\n\n`currency` escolhe a moeda de TODOS os blocos `costs` da resposta: `brl` (padrão) ou `usd`. A resposta ecoa a escolha em `currency` (`BRL` ou `USD`) e em `applied_filters.currency`. Para ver a outra moeda, faça uma nova chamada com `currency` diferente — contagens (`sent`, `resolved`, `pending`…) são as mesmas nas duas.\n\nO custo canônico é o US$ da tarifa da Meta. O R$ é ESTIMADO: tarifa em US$ × PTAX de venda do Banco Central do dia local da mensagem (`basis` inclui `usd_rate_x_ptax_venda`). Enquanto a PTAX do dia não é publicada, a linha fica `pending` em R$.\n\n`basis` lista, sem repetição e em ordem alfabética, as bases de preço dos envios do bloco (`rate_card_base_tier` para tarifa oficial, `usd_rate_x_ptax_venda` para R$ estimado pela PTAX, `historical_reference` para preço histórico informado pelo cliente); vem `[]` quando o bloco não tem custo. As linhas históricas do DFImóveis usam a referência validada pelo cliente (`historical_reference`) também em R$, não a PTAX.\n\n`by_category` separa cada categoria por `pricing_window` e `template_category`. `category: service` com `pricing_window: customer_service_24h` é template enviado grátis dentro da janela de atendimento de 24 h, e `template_category` guarda a categoria original do template. `pricing_window: free_entry_point_72h` é a janela gratuita de 72 h aberta por anúncio (ponto de entrada) — NÃO é a janela de 24 h e mantém a categoria do template. `pricing_window: null` é envio sem janela gratuita. O filtro `category=service` inclui os templates da janela de 24 h.\n\nRespostas livres de atendente e da IA não entram neste relatório: ele conta só envios de campanha e de template.\n\n`by_source` é limitado a 500 origens; a partir daí a resposta vem com `truncated: true` e o excedente não aparece — refine o período ou os filtros.\n\nTaxas (`delivery_rate`, `read_rate`, `reply_rate`) vêm em percentual de 0 a 100, com 1 casa decimal — não são fração de 0 a 1.\n\n`by_source[].source_key` identifica a origem: `campaign:<campaign_id>` para campanha e `template:<connection_id>:<template_name>` para fluxo de template (`none` no lugar do connection_id quando a mensagem não tem conexão).\n\n`campaigns` é alias de `by_source` (mesmo array) — mantido para compatibilidade com integrações existentes; prefira `by_source` em código novo.\n\n`available_filters` só aparece com `&include=filters` — traz as conexões, campanhas e templates com envio no período, mais o catálogo fixo de categorias e o `period` (start_date/end_date) a que as opções se referem, para popular seletores sem uma segunda chamada. Se essa segunda leitura falhar, a resposta ainda vem 200 sem `available_filters`, com `degraded: ['available_filters']` no corpo.\n\n`available_filters.templates[].category` é a categoria do TEMPLATE cadastrado na Meta; já `by_category[].category` pode ser `service` para envios dentro da janela de atendimento de 24h — são duas superfícies diferentes, não o mesmo dado em dois formatos.\n\nTodo erro 4xx deste endpoint responde `{ success: false, error, code }` — inclusive o 422 de parâmetro inválido.\n\nUma única chamada gera um único `data_as_of` para todos os níveis (totals, by_source, by_category, daily) — os números nunca vêm de instantes diferentes.\n\n`preset` (today, last7, last30 ou custom com from/to) é equivalente a start_date/end_date; qualquer uma das duas formas resolve para o mesmo intervalo e aparece em `period` na resposta, com o `preset` efetivamente usado."}}]}]}