@lionchat/mcp-server 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/instructions.md +14 -1
- package/dist/docs/prompts/create_flow_brainstorm.json +1 -1
- package/dist/docs/prompts/customer_health.json +1 -1
- package/dist/docs/prompts/productivity_report.json +1 -1
- package/dist/docs/prompts/quality_audit.json +1 -1
- package/dist/docs/prompts/stuck_leads.json +1 -1
- package/dist/docs/prompts/team_load_balance.json +1 -1
- package/dist/docs/prompts/weekly_recap.json +1 -1
- package/dist/docs/prompts/whatsapp_template_usage.json +1 -1
- package/dist/docs/resources/api-conventions.md +19 -0
- package/dist/docs/resources/best-practices.md +9 -3
- package/dist/docs/resources/conversation-flows.md +23 -0
- package/dist/docs/resources/data-model.md +107 -23
- package/dist/docs/resources/filtros-e-relatorios.md +49 -1
- package/dist/docs/resources/flowbuilder-design-guide.md +181 -38
- package/dist/docs/resources/flowbuilder-patterns.md +35 -20
- package/dist/docs/resources/formularios-publicos.md +110 -28
- package/dist/docs/resources/glossary.md +26 -1
- package/dist/docs/resources/kanban-deep-dive.md +19 -1
- package/dist/docs/resources/reports-guide.md +186 -0
- package/dist/docs/resources/troubleshooting.md +54 -1
- package/dist/sanitize.d.ts +2 -0
- package/dist/sanitize.js +6 -0
- package/dist/sanitize.js.map +1 -1
- package/dist/tools.js +224 -45
- package/dist/tools.js.map +1 -1
- package/endpoints.json +3281 -103
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
```json
|
|
12
12
|
{
|
|
13
13
|
"nodes": [
|
|
14
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
14
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "conversation_created", "config": {} }] } },
|
|
15
15
|
{ "id": "n2", "type": "send_message", "position": { "x": 370, "y": 300 }, "data": { "label": "Saudação", "messageItems": [
|
|
16
16
|
{ "id": "m1", "type": "text", "content": "Oi {{contact.name|default:'tudo bem?'}} 👋 Como posso ajudar hoje?\n\n1️⃣ Vendas\n2️⃣ Suporte\n3️⃣ Financeiro" }
|
|
17
17
|
] } },
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
```json
|
|
56
56
|
{
|
|
57
57
|
"nodes": [
|
|
58
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
58
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "conversation_created", "config": {} }] } },
|
|
59
59
|
{ "id": "n2", "type": "send_message", "position": { "x": 370, "y": 300 }, "data": { "label": "Pede nome", "messageItems": [
|
|
60
60
|
{ "id": "m1", "type": "text", "content": "Olá! Antes de continuar, qual seu nome completo?" }
|
|
61
61
|
] } },
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
```json
|
|
100
100
|
{
|
|
101
101
|
"nodes": [
|
|
102
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
102
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "message_received", "config": { "keywords": ["proposta","orçamento"], "match_type": "contains" } }] } },
|
|
103
103
|
{ "id": "n2", "type": "send_message", "position": { "x": 370, "y": 300 }, "data": { "label": "B - Budget", "messageItems": [{ "id": "m1", "type": "text", "content": "Pra te ajudar melhor, qual a faixa de investimento que vocês têm em mente?\n\n1️⃣ Até R$ 5k\n2️⃣ R$ 5k a R$ 20k\n3️⃣ Acima de R$ 20k" }] } },
|
|
104
104
|
{ "id": "n3", "type": "wait_response", "position": { "x": 690, "y": 300 }, "data": { "label": "Budget", "waitTime": 60, "waitUnit": "minutes", "validation": "options", "acceptedOptions": ["1","2","3"], "saveTo": "contact_attr", "saveAttrKey": "budget_tier" } },
|
|
105
105
|
{ "id": "n4", "type": "send_message", "position": { "x": 1010, "y": 300 }, "data": { "label": "A - Authority", "messageItems": [{ "id": "m2", "type": "text", "content": "Você é o decisor ou tem outras pessoas envolvidas?" }] } },
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
```json
|
|
151
151
|
{
|
|
152
152
|
"nodes": [
|
|
153
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
153
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "conversation_resolved", "config": {} }] } },
|
|
154
154
|
{ "id": "n2", "type": "wait", "position": { "x": 370, "y": 300 }, "data": { "label": "Aguarda 5 min", "waitTime": 5, "waitUnit": "minutes" } },
|
|
155
155
|
{ "id": "n3", "type": "send_message", "position": { "x": 690, "y": 300 }, "data": { "label": "Pede nota", "messageItems": [{ "id": "m1", "type": "text", "content": "Antes de fechar: de 1 a 5, como foi seu atendimento?\n\n5 = excelente\n1 = ruim" }] } },
|
|
156
156
|
{ "id": "n4", "type": "wait_response", "position": { "x": 1010, "y": 300 }, "data": { "label": "Nota", "waitTime": 60, "waitUnit": "minutes", "validation": "options", "acceptedOptions": ["1","2","3","4","5"], "saveTo": "conversation_attr", "saveAttrKey": "csat_score" } },
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
```json
|
|
183
183
|
{
|
|
184
184
|
"nodes": [
|
|
185
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
185
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "conversation_created", "config": {} }] } },
|
|
186
186
|
{ "id": "n2", "type": "condition", "position": { "x": 370, "y": 300 }, "data": { "label": "Horário comercial?", "conditions": [
|
|
187
187
|
{ "id": "c1", "label": "Dentro do horário", "field": "{{now.hour}}", "operator": "greater_than", "value": "8", "valueType": "variable" }
|
|
188
188
|
] } },
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
```json
|
|
211
211
|
{
|
|
212
212
|
"nodes": [
|
|
213
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
213
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "message_received", "config": {} }] } },
|
|
214
214
|
{ "id": "n2", "type": "ai", "position": { "x": 370, "y": 300 }, "data": {
|
|
215
215
|
"label": "Classifica", "aiMode": "intent",
|
|
216
216
|
"aiPrompt": "Classifique a intenção da última mensagem do cliente",
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
```json
|
|
243
243
|
{
|
|
244
244
|
"nodes": [
|
|
245
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
245
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "label_added", "config": { "label_names": ["follow-up"] } }] } },
|
|
246
246
|
{ "id": "n2", "type": "wait", "position": { "x": 370, "y": 300 }, "data": { "label": "Espera 7 dias", "waitTime": 7, "waitUnit": "days" } },
|
|
247
247
|
{ "id": "n3", "type": "send_message", "position": { "x": 690, "y": 300 }, "data": { "label": "Template re-engajamento", "messageItems": [
|
|
248
248
|
{ "id": "m1", "type": "whatsapp_template", "templateId": 42, "params": ["{{contact.name}}"] }
|
|
@@ -269,7 +269,7 @@
|
|
|
269
269
|
```json
|
|
270
270
|
{
|
|
271
271
|
"nodes": [
|
|
272
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
272
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "message_received", "config": {} }] } },
|
|
273
273
|
{ "id": "n2", "type": "condition", "position": { "x": 370, "y": 300 }, "data": { "label": "Pede humano?", "conditions": [
|
|
274
274
|
{ "id": "c1", "label": "Sim", "field": "{{last_response}}", "operator": "contains", "value": "humano", "valueType": "variable" }
|
|
275
275
|
] } },
|
|
@@ -299,7 +299,7 @@
|
|
|
299
299
|
```json
|
|
300
300
|
{
|
|
301
301
|
"nodes": [
|
|
302
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
302
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "message_received", "config": { "keywords": ["agendar","reunião"], "match_type": "contains" } }] } },
|
|
303
303
|
{ "id": "n2", "type": "send_message", "position": { "x": 370, "y": 300 }, "data": { "label": "Pede data", "messageItems": [{ "id": "m1", "type": "text", "content": "Em que data você prefere? (formato DD/MM)" }] } },
|
|
304
304
|
{ "id": "n3", "type": "wait_response", "position": { "x": 690, "y": 300 }, "data": { "label": "Data", "waitTime": 30, "waitUnit": "minutes", "validation": "regex", "regexPattern": "^\\d{2}/\\d{2}$", "invalidMessage": "Formato inválido. Use DD/MM (ex: 25/05)", "maxRetries": 2, "saveTo": "variable", "saveVariable": "data_pref" } },
|
|
305
305
|
{ "id": "n4", "type": "api", "position": { "x": 1010, "y": 300 }, "data": {
|
|
@@ -333,7 +333,7 @@
|
|
|
333
333
|
```json
|
|
334
334
|
{
|
|
335
335
|
"nodes": [
|
|
336
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
336
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "label_added", "config": { "label_names": ["urgente"] } }] } },
|
|
337
337
|
{ "id": "n2", "type": "action", "position": { "x": 370, "y": 300 }, "data": { "label": "Dispara webhook", "items": [
|
|
338
338
|
{ "key": "send_webhook", "config": {
|
|
339
339
|
"url": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
|
|
@@ -364,7 +364,7 @@
|
|
|
364
364
|
{
|
|
365
365
|
"nodes": [
|
|
366
366
|
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [
|
|
367
|
-
{ "
|
|
367
|
+
{ "key": "webhook_received", "config": { "integration_id": 123 } }
|
|
368
368
|
] } },
|
|
369
369
|
{ "id": "n2", "type": "action", "position": { "x": 370, "y": 300 }, "data": { "label": "Marca origem", "items": [
|
|
370
370
|
{ "key": "add_conversation_label", "config": { "labels": ["compra-confirmada"] } }
|
|
@@ -417,7 +417,7 @@
|
|
|
417
417
|
```json
|
|
418
418
|
{
|
|
419
419
|
"nodes": [
|
|
420
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
420
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "message_received", "config": { "keywords": ["orçamento"], "match_type": "contains" } }] } },
|
|
421
421
|
{ "id": "n2", "type": "action", "position": { "x": 370, "y": 300 }, "data": { "label": "Marca", "items": [
|
|
422
422
|
{ "key": "add_conversation_label", "config": { "labels": ["pediu-orcamento"] } },
|
|
423
423
|
{ "key": "add_label", "config": { "labels": ["interessado-em-comprar"] } }
|
|
@@ -440,7 +440,7 @@
|
|
|
440
440
|
```json
|
|
441
441
|
{
|
|
442
442
|
"nodes": [
|
|
443
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
443
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "conversation_created", "config": {} }] } },
|
|
444
444
|
{ "id": "n2", "type": "send_message", "position": { "x": 370, "y": 300 }, "data": { "label": "Oferece", "messageItems": [
|
|
445
445
|
{ "id": "m1", "type": "text", "content": "Quer receber nossas novidades por e-mail?" }
|
|
446
446
|
] } },
|
|
@@ -520,7 +520,7 @@
|
|
|
520
520
|
{
|
|
521
521
|
"nodes": [
|
|
522
522
|
{ "id": "start", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [
|
|
523
|
-
{ "
|
|
523
|
+
{ "key": "date_trigger", "config": {
|
|
524
524
|
"attr_key": "_date_of_birth",
|
|
525
525
|
"offset_direction": "on",
|
|
526
526
|
"offset_days": 0,
|
|
@@ -561,7 +561,7 @@ funciona em conta com caixa **WhatsApp QR Code (`Channel::Waha`)** — a caixa v
|
|
|
561
561
|
```json
|
|
562
562
|
{
|
|
563
563
|
"nodes": [
|
|
564
|
-
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "
|
|
564
|
+
{ "id": "n1", "type": "start", "position": { "x": 50, "y": 300 }, "data": { "label": "Início", "items": [{ "key": "card_won", "config": { "funnel_ids": ["1"] } }] } },
|
|
565
565
|
{ "id": "n2", "type": "update_group", "position": { "x": 370, "y": 300 }, "data": {
|
|
566
566
|
"label": "Cria grupo de onboarding",
|
|
567
567
|
"groupOperation": "create",
|
|
@@ -593,15 +593,25 @@ Comuns a (quase) todas:
|
|
|
593
593
|
|
|
594
594
|
| Operação | Chaves específicas | Retorno (`{{grupo.*}}`) |
|
|
595
595
|
|---|---|---|
|
|
596
|
-
| `create` | `groupName` (obrig), `groupDescription`, `groupPicture` (url), `groupInitialAdmins`, `groupParticipants` | `id`, `name`, `participants_count` |
|
|
597
|
-
| `add_participants` | `groupParticipants` (obrig — ex.: `["5511999999999@c.us"]` ou lista) | `added`, `not_added`, `added_count` |
|
|
596
|
+
| `create` | `groupName` (obrig), `groupDescription`, `groupPicture` (url), `groupInitialAdmins`, `groupParticipants`, `groupAttributes`; opcional `groupInviteOnFailure` + `groupInviteMessage` (com `{{link}}`) | `id`, `name`, `participants_count`, `conversation_id`, `added`, `added_count`, `not_added`, `not_added_count`, `no_whatsapp`, `invited`, `invite_status` — saída `partial` quando alguém ficou de fora |
|
|
597
|
+
| `add_participants` | `groupParticipants` (obrig — ex.: `["5511999999999@c.us"]` ou lista); opcional `groupInviteOnFailure` + `groupInviteMessage` | `added`, `not_added`, `added_count`, `not_added_count`, `no_whatsapp`, `invited`, `invite_status` |
|
|
598
598
|
| `send_invite` | `groupInviteTo` (obrig — telefone de destino), `groupInviteMessage` (opc) | `invite_link` |
|
|
599
|
-
| `settings` | pelo menos uma de `infoAdminOnly` / `messagesAdminOnly` / `membersCanAddNewMember` (booleans) |
|
|
599
|
+
| `settings` | pelo menos uma de `infoAdminOnly` / `messagesAdminOnly` / `membersCanAddNewMember` (booleans) | `settings_updated` (lista do que aplicou), `settings_failed` (`[{setting, reason}]`), `settings_unsupported` |
|
|
600
|
+
| `send_message` | `messageItems` (mesmo contrato do bloco de mensagem: text/delay/attachment/audio/url_media) + `groupTargetId` opcional — manda os balões NA CONVERSA DO GRUPO (vazio = grupo da conversa atual). É a 17ª operação (08/08) | — (única sem variável de resposta) |
|
|
601
|
+
|
|
602
|
+
Os campos `added`/`not_added`/`no_whatsapp`/`invited`/`invite_status` do `create` e `groupInviteOnFailure` são
|
|
603
|
+
de 21/08/2026 (entram com o próximo deploy do app depois de 21/08/2026).
|
|
600
604
|
|
|
601
605
|
**ATENÇÃO na `settings` — semântica INVERTIDA de `membersCanAddNewMember`:** `true` = **TODOS** podem adicionar
|
|
602
606
|
membros. Já `infoAdminOnly: true` e `messagesAdminOnly: true` = **SÓ admin** edita infos / manda mensagem.
|
|
603
607
|
Confundir os dois LIBERA o grupo achando que está trancando.
|
|
604
608
|
|
|
609
|
+
**Cada permissão vale por si (2026-08-20):** o bloco aplica uma a uma; o que aplicou aparece em
|
|
610
|
+
`{{grupo.settings_updated}}` mesmo quando outra falhou, e a saída só é `success` quando algo mudou E
|
|
611
|
+
nada falhou. `membersCanAddNewMember` ("quem pode adicionar") depende do servidor de WhatsApp QR
|
|
612
|
+
Code ser 2026.7+ — no servidor atual ela volta em `settings_unsupported` (não é erro do cliente) e
|
|
613
|
+
as outras duas seguem aplicadas. Não prometa essa permissão ao cliente sem conferir o resultado.
|
|
614
|
+
|
|
605
615
|
**Gatilho novo `group_participant_joined` (entrou no grupo → manda boas-vindas):** em um flow de grupo
|
|
606
616
|
(`conversation_mode: "group"`), o start pode ter o item `{ "key": "group_participant_joined", "config": {
|
|
607
617
|
"group_match": "any", "group_name": "", "group_ids": [] } }`. Filtro OPCIONAL de quais grupos disparam:
|
|
@@ -618,8 +628,13 @@ grupo** (o grupo é a conversa do flow).
|
|
|
618
628
|
- No fluxo de card ganho acima, o `send_message` vai para a conversa do CONTATO (o lead), não para o grupo
|
|
619
629
|
criado. Para postar DENTRO do grupo, use um flow de grupo (`conversation_mode: "group"`) — normalmente com o
|
|
620
630
|
gatilho `group_participant_joined`.
|
|
621
|
-
- Em `add_participants`, quem tem privacidade de grupo ligada pode não entrar mesmo com
|
|
622
|
-
WhatsApp:
|
|
631
|
+
- Em `create` e `add_participants`, quem tem privacidade de grupo ligada pode não entrar mesmo com
|
|
632
|
+
resposta de sucesso do WhatsApp: o bloco relê o grupo e manda quem faltou em `{{grupo.not_added}}`
|
|
633
|
+
(saída `partial`). Se o cliente quiser, ligue `groupInviteOnFailure` com um texto em
|
|
634
|
+
`groupInviteMessage` — o convite vai no privado (máx. 5 por execução, 1 por telefone/dia, 50/dia
|
|
635
|
+
por caixa) e cria contato + conversa pra cada um.
|
|
636
|
+
- Handles do bloco: `success`, `error` e `partial` (em `create`/`add_participants`/`remove_participants` — a
|
|
637
|
+
operação rodou mas nem todo mundo entrou/saiu). `partial` sem fio segue pelo fio `success`.
|
|
623
638
|
|
|
624
639
|
---
|
|
625
640
|
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Tudo sobre os Formulários Públicos do LionChat (captação estilo Typeform/Typebot): link público,
|
|
4
4
|
caixa de WhatsApp vinculada, desenho dos blocos, publicação, respostas, funil, simulador e
|
|
5
|
-
integração com fluxos. Atualizado em
|
|
5
|
+
integração com fluxos. Atualizado em 21/08/2026 (v4.10.228 + commits de 21/08 na elvislion: título
|
|
6
|
+
público, aba Preenchimentos — entram com o próximo deploy do app depois de 21/08/2026).
|
|
6
7
|
|
|
7
8
|
## O que é
|
|
8
9
|
|
|
@@ -51,16 +52,21 @@ caixa de WhatsApp vinculada e (opcionalmente) dispara um fluxo do FlowBuilder no
|
|
|
51
52
|
| `lionchat_lead_forms_responses_show` | `GET /lead_forms/:id/responses/:rid` | Detalhe de uma resposta, com o que a pessoa respondeu |
|
|
52
53
|
| `lionchat_lead_forms_test_run` | `POST /lead_forms/:lead_form_id/test_runs` | Simulador: roda o RASCUNHO no motor real, sem deixar rastro. Qualquer agente da conta |
|
|
53
54
|
| `lionchat_lead_forms_check_embed` | `POST /lead_forms/check_embed` | Confere se um site aceita ser exibido em iframe (bloco Página externa). Admin-only |
|
|
55
|
+
| `lionchat_contacts_form_entries_list` | `GET /contacts/:contact_id/form_entries` | Tudo que UMA PESSOA preencheu, das 4 origens (nosso formulário, Meta, Webhook Universal, Fluxo). Qualquer agente |
|
|
56
|
+
| `lionchat_contacts_form_entries_show` | `GET /contacts/:contact_id/form_entries/:source/:id` | Detalhe de um preenchimento a partir do contato. Qualquer agente |
|
|
54
57
|
|
|
55
|
-
**Imagens não
|
|
56
|
-
máximo 5 MB,
|
|
57
|
-
|
|
58
|
+
**Imagens não sobem pelo conector.** A rota `POST /lead_forms/:id/media` (admin, multipart `file`,
|
|
59
|
+
só `image/*`, máximo 5 MB, devolve `{blob_id, url}`) existe, mas upload de arquivo não passa pelo MCP
|
|
60
|
+
— chamada pelo conector ela volta 422 `invalid_file_type`. Suba a imagem no editor do painel e
|
|
61
|
+
referencie o `blob_id` (o `show` traz `media_urls` com todos os ids do acervo: imagem de bloco,
|
|
62
|
+
imagem de opção e a foto do chat). **Imagem subida e não referenciada em até 1 hora é apagada** na
|
|
63
|
+
próxima varredura (todo `update`/`publish` roda a varredura).
|
|
58
64
|
|
|
59
65
|
## Campos do formulário
|
|
60
66
|
|
|
61
67
|
| Campo | Tipo | Observação |
|
|
62
68
|
|---|---|---|
|
|
63
|
-
| `name` | string | **Obrigatório
|
|
69
|
+
| `name` | string | **Obrigatório.** Nome INTERNO (lista do painel). O que o lead vê na aba do navegador e no link compartilhado é `settings.public_title`; vazio, cai no `name` |
|
|
64
70
|
| `display_mode` | string | `cards` (padrão) ou `chat` |
|
|
65
71
|
| `active` | boolean | Formulário inativo devolve 404 na página pública |
|
|
66
72
|
| `inbox_id` | integer | Caixa de WhatsApp vinculada. Obrigatória para publicar. Ver regras abaixo |
|
|
@@ -94,6 +100,14 @@ ou `null` — a caixa resolvida), e `published_inbox_id` (a caixa que está NA F
|
|
|
94
100
|
| `button_label` | string — rótulo do botão da página pública | — |
|
|
95
101
|
| `resume` | booleano — continuar de onde parou entre visitas | `true` |
|
|
96
102
|
| `chat_avatar` | id numérico de blob já subido (`media`) — foto redonda ao lado dos balões, só no formato `chat`. O público recebe `chat_avatar_url` resolvida (nunca o id) | — |
|
|
103
|
+
| `public_title` | string, até 100 caracteres — título que o LEAD vê (aba do navegador, link compartilhado). Vazio = usa o `name`. O endpoint público devolve `display_title` (nunca o `name` cru) | — (21/08 — entra com o próximo deploy do app depois de 21/08/2026) |
|
|
104
|
+
|
|
105
|
+
**No `update`, `settings` SUBSTITUI o objeto inteiro** (assim como `form_data`): não há merge. Mande
|
|
106
|
+
todas as chaves que quer manter — um `settings: {theme: "dark"}` apaga cor, avatar, título público,
|
|
107
|
+
`resume` e `abandon_minutes`. E apagar `chat_avatar` custa o arquivo: a varredura de imagens roda
|
|
108
|
+
logo após o salvamento e purga o que não está mais referenciado em `form_data`, na foto publicada ou
|
|
109
|
+
em `settings.chat_avatar` (só upload com menos de 1 hora escapa). O mesmo vale pra `image` de bloco
|
|
110
|
+
ou de opção que sair do `form_data`.
|
|
97
111
|
|
|
98
112
|
## Ciclo de vida
|
|
99
113
|
|
|
@@ -208,8 +222,16 @@ renderiza e não dá erro.
|
|
|
208
222
|
**`options`** (do `choice`): `[{ "id": "abc123", "label": "Sim", "image": "" }]`.
|
|
209
223
|
|
|
210
224
|
**`branches`** (do `condition`): lista avaliada em ordem; nenhum ramo casando cai no handle `else`.
|
|
211
|
-
|
|
212
|
-
|
|
225
|
+
Cada ramo: `{ "id": "b1", "label": "Sim", "source_node_id": "<id da pergunta/escolha/consulta/IA>",
|
|
226
|
+
"operator": "equals", "value": "...", "path": "" }`. `id` é estável (a aresta `cond_<id>` aponta
|
|
227
|
+
pra ele); `label` é o rótulo do fio no desenho (vazio = número). Operadores: `equals`, `not_equals`,
|
|
228
|
+
`contains`, `filled`, `not_filled`, `greater_than`, `less_than` (os dois últimos só numéricos;
|
|
229
|
+
`filled`/`not_filled` ignoram `value`). Fonte Escolha compara pelo **id da opção**, não pelo texto;
|
|
230
|
+
resposta múltipla: `equals` exige exatamente aquela opção, `contains` = inclui. Fonte `api_request`
|
|
231
|
+
exige `path` (caminho dentro da resposta, ex.: `localidade`); fonte `ai` compara o texto/categoria
|
|
232
|
+
direto. **Ramificar por variável** (`{{var.*}}`): o motor aceita `source_variable: "<nome>"` no
|
|
233
|
+
ramo (tem precedência sobre `source_node_id`), mas o dialog de Condição do editor **não conhece a
|
|
234
|
+
chave e a descarta ao salvar o bloco pela tela** — use só em formulário mantido pela API.
|
|
213
235
|
|
|
214
236
|
**`items`** (do `action`) — o formato novo, uma LISTA de ações `[{key, config}]`:
|
|
215
237
|
|
|
@@ -357,6 +379,7 @@ mesmo que exista — é proteção, não defeito.
|
|
|
357
379
|
| Respostas por chamada do simulador | 60 |
|
|
358
380
|
| Altura da Página externa | 240..1200 px |
|
|
359
381
|
| Conferência de embed | 5 s por salto, até 3 redirecionamentos |
|
|
382
|
+
| Lista de preenchimentos do contato | 250 por origem (lead_form / meta_lead / webhook), sem paginação; `meta.truncated` avisa |
|
|
360
383
|
|
|
361
384
|
## Simulador (`lionchat_lead_forms_test_run`)
|
|
362
385
|
|
|
@@ -366,39 +389,90 @@ exige admin. O estado do teste vive em quem chama: cada chamada reproduz o teste
|
|
|
366
389
|
da lista de respostas.
|
|
367
390
|
|
|
368
391
|
- Body: `{answers: [{node_id, value}]}` — até 60 itens; vazio = começa do início. Item cujo
|
|
369
|
-
`node_id` não é o bloco atual PARA a reprodução ali (devolve o bloco onde parou).
|
|
392
|
+
`node_id` não é o bloco atual PARA a reprodução ali (devolve o bloco onde parou). `value: null`
|
|
393
|
+
avança bloco sem resposta (`start`, `message`, `embed`).
|
|
370
394
|
- Resposta 200: `{block, log, answers, variables, form}` — `block` é o mesmo envelope da página
|
|
371
|
-
pública; `log` é o caminho percorrido (`[{node_id, type, label, answer
|
|
372
|
-
mostra variável criada, ações executadas
|
|
373
|
-
|
|
374
|
-
|
|
395
|
+
pública; `log` é o caminho percorrido (`[{node_id, type, label, answer?, detail?}]` — `detail`
|
|
396
|
+
mostra variável criada, ações executadas e o rumo das condições; `answer`/`detail` só aparecem
|
|
397
|
+
quando existem); `form.name` é o nome interno.
|
|
398
|
+
- 422: `{error: "invalid_value", reason}` (uma resposta reprovada — `reason` é o código:
|
|
399
|
+
`required`, `invalid_email`, `invalid_phone`, `invalid_cpf`, `invalid_option`, `value_too_long`…)
|
|
400
|
+
ou `{error: "test_run_failed", reason}`.
|
|
401
|
+
|
|
402
|
+
**O que o simulador NÃO faz (e como isso aparece):**
|
|
403
|
+
|
|
404
|
+
| Bloco | No simulador |
|
|
405
|
+
|---|---|
|
|
406
|
+
| Consulta externa (`api_request`) e IA (`ai`) | **Não executam** (o job de efeito é desligado no modo de teste). Ao chegar neles a resposta volta `block: {node_id, type: "wait"}` e a reprodução para ali — itens seguintes são ignorados e `detail` desses blocos fica vazio. Pra testar o que vem depois, use a página pública (link identificado/aba anônima). |
|
|
407
|
+
| Agendamento (`booking`) | `value: ""` segue por `no_booking` (só se o bloco não for obrigatório). Pra simular "agendou", mande o id de um agendamento **já existente** da conta — o simulador pela API não cria reserva. (No painel, o simulador embute a página real: uma reserva feita ali é de verdade e fica no banco.) |
|
|
408
|
+
| Envio de arquivo (`file_upload`) obrigatório | Não tem como passar (o valor precisa ser um arquivo anexado à resposta de teste, que não existe): 422 `invalid_value`. Só com `required: false` e `value: ""`. |
|
|
409
|
+
| Envio de WhatsApp, etiqueta, pixel, marco | Aparecem no `log` como reservados (`detail` lista as ações), mas nada é enviado/gravado. |
|
|
375
410
|
|
|
376
411
|
## Respostas e funil
|
|
377
412
|
|
|
378
|
-
**`lionchat_lead_forms_stats`** (`days` opcional) devolve
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
413
|
+
**`lionchat_lead_forms_stats`** (`days` opcional, default 30, máximo 90) devolve
|
|
414
|
+
`views` (contador de VIDA INTEIRA do formulário — não obedece `days`), `starts`/`completed`/
|
|
415
|
+
`abandoned` da janela, `completion_rate` (% inteiro = completed/starts), `funnel` por bloco e
|
|
416
|
+
`milestones`. O funil lista os blocos que a pessoa vê (`start`, `question`, `choice`, `message`,
|
|
417
|
+
`booking`, `embed`, `end`), na ordem do desenho **publicado**; acima de 50.000 respostas na janela
|
|
418
|
+
sai parcial. **`milestones` hoje só enxerga bloco de Ações no formato antigo (`action_type:
|
|
419
|
+
"milestone"`)** — marco configurado em `items` (formato atual do editor) não aparece aqui; aparece
|
|
420
|
+
em `responses_show.milestones`.
|
|
382
421
|
|
|
383
422
|
**`lionchat_lead_forms_responses_list`** aceita `status` (`in_progress`, `completed`,
|
|
384
|
-
`abandoned`), `days` e `page
|
|
385
|
-
current_node_label, answered_count, created_at, completed_at}
|
|
386
|
-
`{id: null, name, phone, pending: true}`** — é
|
|
387
|
-
três momentos); nome e telefone saem do que a
|
|
423
|
+
`abandoned`), `days` e `page` (até 10.000). A lista nunca traz as respostas em si — só `{id, status,
|
|
424
|
+
contact, current_node_label, answered_count, created_at, completed_at}` e `meta: {current_page,
|
|
425
|
+
per_page: 25, total_count}`. **`contact` pode vir `{id: null, name, phone, pending: true}`** — é
|
|
426
|
+
quem ainda está preenchendo (o contato só nasce nos três momentos); nome e telefone saem do que a
|
|
427
|
+
pessoa já digitou. `answered_count` conta todas as chaves de `answers`, inclusive as sintéticas.
|
|
388
428
|
|
|
389
429
|
**`lionchat_lead_forms_responses_show`** traz o conteúdo:
|
|
390
430
|
|
|
391
|
-
- `
|
|
392
|
-
|
|
431
|
+
- `status`, `contact` (mesmo shape da lista), `current_node_label`, `started_at`, `completed_at`,
|
|
432
|
+
`abandoned_at`, `created_at`.
|
|
433
|
+
- `answers`: `[{label, value_label, at, download_url?}]`, ordenado por `at`. **Atenção:**
|
|
434
|
+
(1) o bloco de Ações grava entradas SINTÉTICAS com chave `"<node_id>::<n>"` e o fim degradado
|
|
435
|
+
(beco sem saída/teto de passos) grava `"_walk_ended"` — nem toda chave é id de bloco;
|
|
436
|
+
(2) no bloco de **Agendamento `value_label` é o id da reserva, não a data** (a aba Preenchimentos
|
|
437
|
+
do contato mostra a data); (3) envio de WhatsApp pulado por falta de telefone aparece como
|
|
438
|
+
entrada vazia do bloco de mensagem.
|
|
393
439
|
- `conversation`: `{id, display_id}` ou `null` — a conversa aberta na caixa vinculada.
|
|
394
|
-
- `milestones`: os marcos atingidos.
|
|
395
|
-
- `effects`: resultado de consulta/IA — `{node_id, label, state, error, status,
|
|
396
|
-
`url_short` é esquema + host + caminho, sem a query; corpo, cabeçalhos e prompt
|
|
440
|
+
- `milestones`: os marcos atingidos (`[{node_id, name, at}]`).
|
|
441
|
+
- `effects`: resultado de consulta/IA — `{node_id, label, state: pending|done|error, error, status,
|
|
442
|
+
url_short, at}`. `url_short` é esquema + host + caminho, sem a query; corpo, cabeçalhos e prompt
|
|
443
|
+
nunca saem.
|
|
397
444
|
- `utm_params`: de onde o lead veio.
|
|
398
445
|
|
|
399
|
-
Motivos de erro em `effects`: `http_<código
|
|
400
|
-
|
|
401
|
-
`
|
|
446
|
+
Motivos de erro em `effects.error`: `invalid_method`, `invalid_url`, `http_<código>` (`http_0` =
|
|
447
|
+
falha de transporte sem código), `timeout`, `ssrf_blocked`, `result_too_large`, `stale`,
|
|
448
|
+
`effect_failed` (exceção no job), `effect_runs_exceeded` (laço: 20 execuções), `budget_exceeded`,
|
|
449
|
+
`no_ai_key`, `ai_error`. (Os motivos `busy` e `unresolved_variable`, citados antes, não existem no
|
|
450
|
+
código.)
|
|
451
|
+
|
|
452
|
+
## Preenchimentos do contato (`lionchat_contacts_form_entries_list` / `_show`) — 21/08 (entra com o próximo deploy do app depois de 21/08/2026)
|
|
453
|
+
|
|
454
|
+
A aba "Preenchimentos" da ficha junta, numa lista só ordenada por data, tudo que a pessoa preencheu
|
|
455
|
+
ou que entrou por ela, de QUATRO origens: nosso formulário público (`source: lead_form`),
|
|
456
|
+
formulário de anúncio do Meta (`meta_lead`), Webhook Universal (`custom_webhook`) e o webhook que o
|
|
457
|
+
FlowBuilder cria pro gatilho de um fluxo (`flow`, com o nome do fluxo). É a única rota que responde
|
|
458
|
+
"o que esta PESSOA preencheu" — as outras são por formulário/integração, e duas delas são só de
|
|
459
|
+
administrador; esta é liberada a qualquer agente.
|
|
460
|
+
|
|
461
|
+
- **Lista**: `{payload: [{source, id, title, status, at, count, conversation}], meta: {total,
|
|
462
|
+
truncated}}`. `id` é o id do registro NA ORIGEM — só identifica junto com `source`. `status` é
|
|
463
|
+
`in_progress|completed|abandoned` no nosso formulário e `received` nas demais. `count` = respostas
|
|
464
|
+
(nosso) ou campos vinculados e gravados (de fora). `conversation` só existe em `lead_form`. Sem
|
|
465
|
+
paginação de propósito; teto de 250 por origem — estourou, `truncated: true`. Só entram eventos
|
|
466
|
+
do Meta/webhook com status processado.
|
|
467
|
+
- **Detalhe** (`source` + `id` da mesma linha): nosso formulário devolve `answers` (`[{label,
|
|
468
|
+
value, at, download_url?, images?}]` — pergunta/resposta legíveis, na ordem; Escolha com opção
|
|
469
|
+
ilustrada traz `images`, e opção sem texto vem com `value` vazio; Agendamento mostra a DATA),
|
|
470
|
+
`written` (`[{scope: contact|conversation, label, value}]` — o que AQUELE preenchimento gravou) e
|
|
471
|
+
`utm`. Meta/webhook/fluxo devolvem SÓ `fields: [{label, value}]` — os campos que foram vinculados a
|
|
472
|
+
atributos daqui e realmente gravados, com o rótulo daqui; nunca o payload cru. `fields: []` = o
|
|
473
|
+
evento não gravou nada. Origem fora da lista ou registro de outro contato = 404.
|
|
474
|
+
- A lista **não traz o `lead_form_id`** da resposta: pra chegar em `lionchat_lead_forms_responses_show`
|
|
475
|
+
a partir dela, ache o formulário pelo `title` em `lionchat_lead_forms_list`.
|
|
402
476
|
|
|
403
477
|
## Conferência de iframe (`lionchat_lead_forms_check_embed`)
|
|
404
478
|
|
|
@@ -497,3 +571,11 @@ agendar (com `required: true` essa saída deixa de existir). O tipo de evento ve
|
|
|
497
571
|
- **Não há tool de rewind**: o "Voltar e corrigir" é da página pública (o lead corrige a própria
|
|
498
572
|
resposta; telefone corrigido reencaminha a mensagem pro número certo). Pela API de dashboard,
|
|
499
573
|
respostas são somente leitura.
|
|
574
|
+
- **`settings` no `update` também SUBSTITUI o objeto inteiro** — e sem `chat_avatar` o arquivo da
|
|
575
|
+
foto é apagado pela varredura. Leia com `show`, altere e mande completo.
|
|
576
|
+
- **Imagem que some do `form_data` é apagada de verdade** logo após o salvamento (só sobrevive se
|
|
577
|
+
ainda estiver na foto publicada ou tiver menos de 1 hora).
|
|
578
|
+
- **O simulador não roda consulta nem IA**: parou em `block.type: "wait"` = chegou num desses
|
|
579
|
+
blocos. Teste o trecho seguinte pela página pública.
|
|
580
|
+
- **`stats.views` é de vida inteira** (ignora `days`); `stats.milestones` não vê marco em `items`.
|
|
581
|
+
- **`name` é interno; `settings.public_title` é o que o lead vê** (vazio = cai no `name`).
|
|
@@ -74,6 +74,15 @@ Glossário completo de termos, status codes, enums e conceitos da plataforma. Us
|
|
|
74
74
|
de WhatsApp — oficial e QR Code). A bolha mostra o selo "Encaminhada"
|
|
75
75
|
- `content_attributes.frequently_forwarded`: `true` = encaminhada MUITAS vezes (o "encaminhada com
|
|
76
76
|
frequência" do WhatsApp). Útil pra responder "essa mensagem foi encaminhada?" sem chutar
|
|
77
|
+
- `content_attributes.edited`: `true` = o CLIENTE editou a mensagem no WhatsApp (`edited_at` traz
|
|
78
|
+
quando). O `content` já é o texto novo; a versão anterior não fica guardada. Vale nos dois canais
|
|
79
|
+
de WhatsApp (QR Code desde 18/07/2026; oficial desde 21/08/2026 — entra com o próximo deploy do
|
|
80
|
+
app depois de 21/08/2026; antes a edição virava um balão VAZIO novo, contado como mensagem nova).
|
|
81
|
+
- `content_attributes.deleted`: `true` = mensagem APAGADA ("apagar para todos"), pelo cliente ou
|
|
82
|
+
pela empresa no celular (convivência). O `content` vira `"⛔ Esta mensagem foi excluída"` seguido
|
|
83
|
+
do texto original (a equipe continua vendo o que foi dito); os anexos são removidos. Ao resumir
|
|
84
|
+
uma conversa, trate como apagada — não cite o texto como se ainda estivesse de pé. (No canal
|
|
85
|
+
oficial, desde 21/08/2026 — entra com o próximo deploy do app depois de 21/08/2026.)
|
|
77
86
|
|
|
78
87
|
## Attachment (Anexo)
|
|
79
88
|
|
|
@@ -296,6 +305,18 @@ comportamento intencional.
|
|
|
296
305
|
- `role`: `agent` (atendente) ou `administrator` (admin/dono)
|
|
297
306
|
- `availability`: `online`, `busy`, `offline`
|
|
298
307
|
- `permissions`: array de strings com permissões granulares
|
|
308
|
+
- `replacing`: `true` em `lionchat_agents_list` por até 30 min enquanto uma substituição
|
|
309
|
+
(`lionchat_agents_replace`) transfere a carteira desse agente pro novo — ele está SAINDO; não
|
|
310
|
+
atribua nada a ele
|
|
311
|
+
|
|
312
|
+
### Agente de SUPORTE (SupportAccessGrant) — não é agente da conta
|
|
313
|
+
|
|
314
|
+
Quem entra na conta por **acesso de suporte** (operador da plataforma) aparece como administrador,
|
|
315
|
+
mas **não é membro da equipe**: `lionchat_agents_list` não o lista (escopo `regular_users`), ele
|
|
316
|
+
não ocupa vaga do plano, não pode ser substituído (`agents_replace` recusa) e, desde 17/08/2026,
|
|
317
|
+
a IA também o ignora — não atribui conversa a ele, não agenda mensagem no nome dele, não o menciona
|
|
318
|
+
em alerta nem o conta como "disponível". Exceção deliberada: se alguém o atribuir à mão como
|
|
319
|
+
responsável, ele segue valendo como responsável.
|
|
299
320
|
|
|
300
321
|
## AutomationRule (Regra de automação)
|
|
301
322
|
|
|
@@ -309,7 +330,11 @@ comportamento intencional.
|
|
|
309
330
|
- `booking_event_type_id`: FK pro tipo de evento (ex: "Demo 30min")
|
|
310
331
|
- `attendee_email`, `attendee_name`, `attendee_phone`
|
|
311
332
|
- `start_time`, `end_time`: ISO 8601
|
|
312
|
-
- `status`: `
|
|
333
|
+
- `status`: `confirmed`, `cancelled`, `completed` — mas **só muda quando o cancelamento/conclusão
|
|
334
|
+
vem pelo link do paciente ou pela IA**. Quem conclui/cancela pela Agenda mexe na TAREFA
|
|
335
|
+
(`account_tasks.status`: `pending`/`completed`/`cancelled`/`snoozed`), e o booking fica
|
|
336
|
+
`confirmed`. Pra saber se um agendamento aconteceu, leia a tarefa (`lionchat_tasks_show`), não o
|
|
337
|
+
booking.
|
|
313
338
|
|
|
314
339
|
**A IA agenda EXCLUSIVAMENTE por agenda de Booking (15/08/2026).** Não existe mais agendamento
|
|
315
340
|
avulso/direto pela IA — ela consulta as agendas liberadas, respeita os dias em que a agenda não
|
|
@@ -48,6 +48,17 @@ Stages NÃO são tabela separada. São armazenadas como jsonb dentro do Funnel:
|
|
|
48
48
|
}
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
**Etapa que não existe no funil (desde 19/08/2026):**
|
|
52
|
+
- `kanban_items_create`/`_update`/`_move` com `funnel_stage` que o funil não tem **não falha e não
|
|
53
|
+
some**: o card é REDIRECIONADO — pro destino gravado quando a etapa foi apagada, senão pra etapa
|
|
54
|
+
da mesma posição, senão pra primeira. **Confira o `funnel_stage` da resposta**: pode ser diferente
|
|
55
|
+
do que você mandou. Leia `funnels_show` antes e use o slug exato.
|
|
56
|
+
- `funnels_update` que tira do `stages` uma etapa com card responde **422 `STAGE_HAS_ITEMS`** com
|
|
57
|
+
`stages: {"<slug>": <quantos cards>}`. Primeiro mova os cards com `lionchat_funnels_create_2`
|
|
58
|
+
(`POST /funnels/{id}/migrate_stage`, `from_stage`, `to_stage` ou `to_funnel_id`) e só então
|
|
59
|
+
reenvie o funil sem a etapa. Não existe parâmetro de destino no `update` de propósito.
|
|
60
|
+
- Funil apagado continua sendo erro (o card não é criado).
|
|
61
|
+
|
|
51
62
|
### KanbanConfig (Configuração Global do Kanban) — CRÍTICO
|
|
52
63
|
|
|
53
64
|
**Tabela separada do Funnel.** 1:1 com a conta. Guarda configurações que valem pra TODOS os funis. Tem 5 campos jsonb importantes:
|
|
@@ -245,6 +256,11 @@ Log automático de eventos do card:
|
|
|
245
256
|
|
|
246
257
|
Tipos comuns: `created`, `stage_changed`, `agent_assigned`, `agent_removed`, `note_added`, `value_changed`, `priority_changed`, `archived`.
|
|
247
258
|
|
|
259
|
+
Quem fez: `by_user_id`/`user` traz a pessoa; ação de automação ou flow vem com o nome da regra/fluxo;
|
|
260
|
+
ação da IA (desde 21/08/2026 — entra com o próximo deploy do app depois de 21/08/2026) vem como
|
|
261
|
+
`"IA: <nome do assistente>"` com `id` nulo — antes saía sem autor. Ao explicar um histórico de card,
|
|
262
|
+
use esse campo pra dizer se foi gente, automação ou IA.
|
|
263
|
+
|
|
248
264
|
## Checklist (tarefas dentro do card)
|
|
249
265
|
|
|
250
266
|
Array plano de itens. Cada item pode (opcionalmente) pertencer a um grupo via `group_id` +
|
|
@@ -580,7 +596,9 @@ Cada card pode receber atributos custom de 3 origens diferentes. **A escolha do
|
|
|
580
596
|
## Diagnóstico: "Card sumiu do funil"
|
|
581
597
|
|
|
582
598
|
1. Está arquivado? `archived = true` em algum nível?
|
|
583
|
-
2. Etapa atual existe ainda?
|
|
599
|
+
2. Etapa atual existe ainda? Desde 19/08/2026 card em etapa inexistente é redirecionado na
|
|
600
|
+
gravação (e os 621 antigos foram movidos pra primeira etapa) — se ainda sumiu, filtre por
|
|
601
|
+
`funnel_stage` com o slug que aparece em `kanban_items_show` e compare com `funnels_show`.
|
|
584
602
|
3. `position` está faltando? (cards sem position vão pro fim)
|
|
585
603
|
4. Está em outro funil? (mover entre funis muda `funnel_id`)
|
|
586
604
|
|