{
  "info": {
    "_postman_id": "550613ba-6863-52fd-81eb-9e687798e482",
    "name": "BoaVoz API v1",
    "description": "Requisições da API pública do BoaVoz (C pública), com testes automáticos.\n\n**Antes de começar:** importe também o ambiente `BoaVoz`, selecione-o no canto superior direito e preencha `api_key`, `bot_id` e `phone`.\n\n- A autenticação (Bearer) e o cabeçalho da proteção da Vercel saem do ambiente, em todas as requisições.\n- As listagens guardam os ids no ambiente: rode \"Listar conversas\" primeiro.\n- Erros vêm no formato `{\"error\": {\"code\", \"message\", \"details\"}}` e aparecem no Console do Postman.\n- A API faz parte do pacote Integrações da conta: sem ele, toda requisição volta `403 plan_not_allowed` (a chave continua guardada).\n- A pasta 10 (Cobrança) pede a permissão `billing` na chave; trocar para um tamanho maior e comprar crédito cobram no cartão da assinatura.\n- A pasta 11 (Fontes) pede a permissão `sources` na chave.\n- A pasta 12 (Lojas) pede a permissão `provisioning`, o pacote Plataforma e, para criar cliente, uma chave com escopo da conta inteira; preencha `template_id` com o id de um modelo. A equipe do portal pede também a permissão `members` e o `member_email` (um e-mail seu: o convite chega nele).",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{api_key}}",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// proteção da Vercel: só o dev.boavoz.com pede (fica vazio em produção)",
          "const bypass = pm.variables.get(\"vercel_bypass\");",
          "if (bypass) pm.request.headers.upsert({ key: \"x-vercel-protection-bypass\", value: bypass });"
        ]
      }
    },
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// erro da API: código, mensagem e detalhes no Console do Postman",
          "if (pm.response.code >= 400) {",
          "  try {",
          "    const e = pm.response.json().error;",
          "    console.log(pm.info.requestName, pm.response.code, e.code, e.message, JSON.stringify(e.details || {}));",
          "  } catch (_) {}",
          "}"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "1. Conversas",
      "description": "Leitura das conversas e do histórico (permissão **conversations**). A listagem guarda no ambiente o id da conversa mais recente de cada canal (`whatsapp_conversation_id`, `instagram_conversation_id`, `widget_conversation_id`), o contato delas e o `conversation_id` usado nas pausas (o do WhatsApp, quando há).",
      "item": [
        {
          "name": "Listar conversas (guarda os ids)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/conversations?limit=20",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "20"
                },
                {
                  "key": "channel",
                  "value": "whatsapp",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "ai_paused",
                  "disabled": true
                },
                {
                  "key": "since",
                  "value": "2026-10-01T00:00:00Z",
                  "disabled": true
                },
                {
                  "key": "contact",
                  "value": "phone:{{phone}}",
                  "disabled": true
                },
                {
                  "key": "bot_id",
                  "value": "{{bot_id}}",
                  "disabled": true
                }
              ]
            },
            "description": "Mais recente primeiro. Filtros (ligue na aba Params): `channel` (widget, whatsapp, instagram), `status` (ai, ai_paused, waiting_human, human), `since`, `contact` e `bot_id`. Paginação por `next_cursor`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "const data = body.data || [];",
                  "for (const ch of [\"whatsapp\", \"instagram\", \"widget\"]) {",
                  "  const c = data.find((x) => x.channel === ch);",
                  "  if (!c) continue;",
                  "  pm.environment.set(ch + \"_conversation_id\", c.id);",
                  "  if (c.contact) pm.environment.set(ch + \"_contact\", c.contact.id);",
                  "}",
                  "const main = data.find((x) => x.channel === \"whatsapp\") || data[0];",
                  "if (main) pm.environment.set(\"conversation_id\", main.id);",
                  "pm.environment.set(\"next_cursor\", body.next_cursor || \"\");",
                  "console.log(\"conversas:\", data.map((c) => `${c.channel} ${c.status} ${c.id}`).join(\"\\n\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Próxima página",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/conversations?limit=20&cursor={{next_cursor}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "20"
                },
                {
                  "key": "cursor",
                  "value": "{{next_cursor}}"
                }
              ]
            },
            "description": "Usa o `next_cursor` da listagem anterior."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.environment.set(\"next_cursor\", pm.response.json().next_cursor || \"\");"
                ]
              }
            }
          ]
        },
        {
          "name": "Só as pausadas pela integração",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/conversations?status=ai_paused",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "ai_paused"
                }
              ]
            },
            "description": ""
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Histórico da conversa",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/conversations/{{conversation_id}}/messages?limit=50",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations",
                "{{conversation_id}}",
                "messages"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Da mais antiga para a mais nova, com status (received, sent, delivered, read, failed, uncertain) e error. Guarda o id da última mensagem em `message_id`. Mensagem com arquivo traz `media`: no áudio, a transcrição; na foto que a IA leu, o `reading` (tipo, descrição e texto da foto)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const data = pm.response.json().data || [];",
                  "if (data.length) pm.environment.set(\"message_id\", data[data.length - 1].id);",
                  "const comArquivo = data.filter((m) => m.media).pop();",
                  "if (comArquivo) pm.environment.set(\"media_id\", comArquivo.media.id);",
                  "const lida = data.filter((m) => m.media && m.media.reading).pop();",
                  "if (lida) console.log(`foto lida pela IA (${lida.media.reading.type}): ${lida.media.reading.description || '-'}`);",
                  "console.log(data.map((m) => `${m.direction} ${m.source} ${m.status}: ${(m.text || \"\").slice(0, 60)}`).join(\"\\n\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Uma mensagem",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/messages/{{message_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages",
                "{{message_id}}"
              ]
            },
            "description": "Permissão **messages**. O status também chega por webhook (message.status) e no histórico. Mensagem com arquivo traz `media` (id, tipo, tamanho, nome e um link de 1 hora; no áudio, a transcrição; na foto que a IA leu, o `reading`)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Baixar arquivo",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/media/{{media_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "media",
                "{{media_id}}"
              ]
            },
            "description": "O arquivo da última mensagem com mídia do histórico (`media_id`), decifrado. Clique em **Save Response** para salvar. Depois do prazo de guarda (ou da exclusão), 410."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"tem o tipo do arquivo\", () => pm.expect(pm.response.headers.get(\"Content-Type\")).to.be.a(\"string\"));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "2. Pausa da IA",
      "description": "A integração pausa a IA numa conversa (sem avisar a equipe). Toda pausa tem prazo: sem `minutes`, 24 horas renovadas a cada mensagem enviada pela API; com `minutes`, de 1 a 10.080. Usa `{{conversation_id}}` (rode a listagem antes).",
      "item": [
        {
          "name": "Pausar por 10 minutos",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/conversations/{{conversation_id}}/pause",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations",
                "{{conversation_id}}",
                "pause"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"minutes\": 10\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"tem ai_paused_until\", () => pm.expect(pm.response.json().ai_paused_until).to.be.a(\"string\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Pausar 24 h com anúncio (Ana)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/conversations/{{conversation_id}}/pause",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations",
                "{{conversation_id}}",
                "pause"
              ]
            },
            "description": "A primeira mensagem enviada pela API sai com o anúncio de entrada com o nome; na volta, a IA usa o \"Voltei!\".",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"announce\": true,\n  \"agent_name\": \"Ana\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Pausar por 1 minuto (testar o vencimento)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/conversations/{{conversation_id}}/pause",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations",
                "{{conversation_id}}",
                "pause"
              ]
            },
            "description": "Mande uma mensagem pelo celular durante a pausa e espere uns 2 minutos: a IA responde sozinha (handoff.returned com reason timeout).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"minutes\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Retomar",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/conversations/{{conversation_id}}/resume",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations",
                "{{conversation_id}}",
                "resume"
              ]
            },
            "description": "A IA responde a última mensagem do contato se ela ficou sem resposta (handoff.returned com reason api).",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Retomar com anúncio",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/conversations/{{conversation_id}}/resume",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations",
                "{{conversation_id}}",
                "resume"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"announce\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "3. Enviar mensagens",
      "description": "POST /v1/messages (permissão **messages**). Cada envio leva uma Idempotency-Key nova; a requisição \"Repetir com a mesma chave\" mostra a idempotência. WhatsApp: mande um \"oi\" do seu celular antes, para abrir a janela de 24 horas.",
      "item": [
        {
          "name": "Texto no WhatsApp (pelo telefone)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"Teste da API do BoaVoz\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Texto assinado (agent_name e sender)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Chega como \"*Ana:* …\". No webhook message.sent: source api, agent.display_name Ana, agent.sender postman e o key_prefix.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"Seu pedido saiu para entrega.\",\n  \"agent_name\": \"Ana\",\n  \"sender\": \"postman\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Texto no WhatsApp (pelo contato)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"{{whatsapp_contact}}\",\n  \"type\": \"text\",\n  \"text\": \"Teste pelo id do contato\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Texto no Instagram",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Usa o contato da conversa de Instagram mais recente (rode a listagem antes). A pessoa precisa ter escrito nas últimas 24 horas.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"{{instagram_contact}}\",\n  \"type\": \"text\",\n  \"text\": \"Teste da API pelo Instagram\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Texto no chat do site",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "O visitante vê em segundos (o widget busca as novidades). Abra o chat do site e mande uma mensagem antes.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"conversation_id\": \"{{widget_conversation_id}}\",\n  \"type\": \"text\",\n  \"text\": \"Oi! Aqui é o sistema integrado.\",\n  \"agent_name\": \"Ana\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Imagem no WhatsApp (com legenda)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "JPEG ou PNG até 5 MB. O BoaVoz baixa a URL, confere o tipo e guarda uma cópia; a Meta busca o arquivo na URL. `text` vira a legenda.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"image\",\n  \"image\": {\n    \"url\": \"{{image_url}}\"\n  },\n  \"text\": \"Foto do seu pedido\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Documento no WhatsApp",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "PDF, Word, Excel, PowerPoint, TXT ou CSV, até 16 MB. `filename` é o nome que o contato vê.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"document\",\n  \"document\": {\n    \"url\": \"{{document_url}}\",\n    \"filename\": \"Boleto teste.pdf\"\n  },\n  \"text\": \"Segue o seu boleto\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Documento no Instagram (PDF)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "No Instagram, arquivo só em PDF e imagem em PNG ou JPEG (até 8 MB). O Instagram não tem legenda: o texto chega numa DM antes do arquivo.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"{{instagram_contact}}\",\n  \"type\": \"document\",\n  \"document\": {\n    \"url\": \"{{document_url}}\"\n  },\n  \"text\": \"Segue o PDF\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Imagem no chat do site",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "O visitante vê a imagem no balão da Ana (clicar abre ampliada). Documento no site: só PDF.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"conversation_id\": \"{{widget_conversation_id}}\",\n  \"type\": \"image\",\n  \"image\": {\n    \"url\": \"{{image_url}}\"\n  },\n  \"text\": \"Olha a foto\",\n  \"agent_name\": \"Ana\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Modelo do WhatsApp",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Funciona também fora da janela de 24 horas. Ajuste `variables` ao número de variáveis do modelo (veja em \"Modelos do bot\"); modelo sem variável: `[]`. Chatbot sem tamanho: 422 bot_without_size.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"template\",\n  \"template\": {\n    \"name\": \"{{template_name}}\",\n    \"language\": \"{{template_language}}\",\n    \"variables\": [\n      \"{{template_variable_1}}\"\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Repetir com a mesma chave (idempotência)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotency_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Rode duas vezes: a segunda devolve o mesmo message_id, com o cabeçalho Idempotent-Replayed: true, e só uma mensagem chega. Troque `idempotency_key` no ambiente para testar de novo.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"Mensagem idempotente\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "pm.test(\"replay na segunda vez\", () => { if (pm.response.headers.get(\"Idempotent-Replayed\")) pm.expect(pm.response.headers.get(\"Idempotent-Replayed\")).to.eql(\"true\"); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "4. Modelos",
      "description": "Modelos do WhatsApp do bot (permissão **messages**).",
      "item": [
        {
          "name": "Modelos do bot",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/templates",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "templates"
              ]
            },
            "description": "Com categoria, status da Meta, número de variáveis e se dá para enviar. Guarda o primeiro modelo enviável em `template_name` e `template_language`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const all = pm.response.json().data || [];",
                  "const t = all.find((x) => x.sendable);",
                  "if (t) { pm.environment.set(\"template_name\", t.name); pm.environment.set(\"template_language\", t.language); }",
                  "const mkt = all.find((x) => x.sendable && x.category === \"marketing\");",
                  "if (mkt) pm.environment.set(\"marketing_template\", mkt.name);",
                  "const utl = all.find((x) => x.sendable && x.category === \"utility\");",
                  "if (utl) pm.environment.set(\"reminder_template\", utl.name);",
                  "console.log((pm.response.json().data || []).map((x) => `${x.name} (${x.language}) ${x.category} ${x.status} vars=${x.variables}${x.sendable ? \"\" : \" — não enviável\"}`).join(\"\\n\"));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "5. Contatos e pareamento",
      "description": "Contatos, etiquetas, leads e idade (permissão **contacts**), aceite de novidades (permissão **campaigns**) e pareamento com o sistema do cliente (permissão **pairing**).",
      "item": [
        {
          "name": "Listar contatos",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/contacts?limit=20",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "20"
                },
                {
                  "key": "channel",
                  "value": "whatsapp",
                  "disabled": true
                },
                {
                  "key": "tag",
                  "value": "vip",
                  "disabled": true
                },
                {
                  "key": "since",
                  "value": "2026-10-01T00:00:00Z",
                  "disabled": true
                },
                {
                  "key": "bot_id",
                  "value": "{{bot_id}}",
                  "disabled": true
                }
              ]
            },
            "description": "Na ordem da última alteração (para sincronizar: guarde o `updated_at` do último e use em `since`). Cada contato vem com etiquetas, consentimento (`consent.marketing` e `consent.opted_out`), `age_confirmed` e as datas das réguas (`birthday`, `last_visit_at` e `appointment_at`)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const data = pm.response.json().data || [];",
                  "const wa = data.find((c) => c.channel === \"whatsapp\");",
                  "if (wa && !pm.variables.get(\"whatsapp_contact\")) pm.environment.set(\"whatsapp_contact\", wa.id);",
                  "console.log(data.map((c) => `${c.channel} ${c.id} ${c.name || \"\"} tags=${c.tags.join(\"|\")} promo=${c.consent ? c.consent.marketing : \"-\"}`).join(\"\\n\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Etiquetas do contato",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/contacts/{{whatsapp_contact}}/tags",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "{{whatsapp_contact}}",
                "tags"
              ]
            },
            "description": "Troca todas as etiquetas (lista vazia apaga). Com `ext:<id>`, vale para todos os contatos do bot com aquele id externo.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"tags\": [\n    \"vip\",\n    \"postman\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Datas do contato (réguas)",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/contacts/phone:{{phone}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "phone:{{phone}}"
              ]
            },
            "description": "As datas que as réguas usam: `birthday` (MM-DD; com a data inteira, o ano não é guardado), `last_visit_at` (a última visita ou compra, no passado) e `appointment_at` (o próximo horário marcado, com fuso, até um ano à frente). O campo que não vem não muda; `null` apaga. Pelo telefone, cria o contato do WhatsApp que ainda não existe. Ajuste `appointment_at` para uma data futura. As datas novas das réguas personalizadas vão em `dates`, pela chave (ex.: `{\"renovacao_do_plano\": \"2027-03-10\"}`); a chave nasce com a régua no painel. Sem nenhuma régua com data nova no chatbot, tire o `dates` do corpo.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"birthday\": \"10-21\",\n  \"last_visit_at\": \"2026-10-05\",\n  \"appointment_at\": \"2026-12-20T14:30:00-03:00\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const c = (pm.response.json().data || [])[0] || {}; console.log(\"aniversário:\", c.birthday, \"visita:\", c.last_visit_at, \"horário:\", c.appointment_at);"
                ]
              }
            }
          ]
        },
        {
          "name": "Aceite de novidades (sim)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/contacts/phone:{{phone}}/consent",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "phone:{{phone}}",
                "consent"
              ]
            },
            "description": "Permissão **campaigns**. Grava o aceite com a prova (origem, texto e data). Cria o contato do WhatsApp se ele ainda não existe. Quem pediu para sair continua fora (`opted_out: true`): só um novo sim dele no chat tira do descadastro.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"channel\": \"whatsapp\",\n  \"granted\": true,\n  \"source\": \"Teste pelo Postman\",\n  \"text\": \"Aceito receber novidades e promoções por WhatsApp\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "const r = pm.response.json(); console.log(\"consent:\", r.consent_id, \"opted_out:\", r.opted_out);"
                ]
              }
            }
          ]
        },
        {
          "name": "Retirar o aceite",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/contacts/phone:{{phone}}/consent",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "phone:{{phone}}",
                "consent"
              ]
            },
            "description": "Revoga o aceite ativo e sai o webhook contact.opted_out (source api).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"channel\": \"whatsapp\",\n  \"granted\": false,\n  \"source\": \"Pedido pelo atendimento\",\n  \"text\": \"Não quero mais receber promoções\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));"
                ]
              }
            }
          ]
        },
        {
          "name": "Leads",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/leads?limit=20",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "leads"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "20"
                },
                {
                  "key": "channel",
                  "value": "whatsapp",
                  "disabled": true
                },
                {
                  "key": "since",
                  "value": "2026-10-01T00:00:00Z",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "2026-10-15T00:00:00Z",
                  "disabled": true
                },
                {
                  "key": "bot_id",
                  "value": "{{bot_id}}",
                  "disabled": true
                }
              ]
            },
            "description": "Um lead por contato em cada chatbot, do mais antigo para o mais novo: nome, telefone, e-mail, o pedido mais recente (`notes`) e o histórico (`requests`, com data, canal, conversa e o que a pessoa pediu). `updated_since` traz só os que mudaram (pedido novo ou contato) desde a data; o webhook lead.updated avisa na hora. Paginação por `next_cursor`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Idade confirmada pela empresa",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/contacts/phone:{{phone}}/age",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "phone:{{phone}}",
                "age"
              ]
            },
            "description": "O contato passa a ter 18+ confirmado (origem company).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"age_verified\": true,\n  \"origin\": \"Teste pelo Postman\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Criar código de pareamento",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/pairings",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "pairings"
              ]
            },
            "description": "Devolve o código de 6 caracteres e o link (vale 10 minutos). O pareamento só termina quando a pessoa abre o link no celular (ele abre o WhatsApp do bot com \"Conectar <código>\" escrito), manda a mensagem e confirma. Guarda o código e o link no ambiente (`pairing_code`, `pairing_link`).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"channel\": \"whatsapp\",\n  \"external_id\": \"{{external_id}}\",\n  \"display\": {\n    \"name\": \"Teste Postman\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "const r = pm.response.json();",
                  "pm.environment.set(\"pairing_code\", r.code);",
                  "pm.environment.set(\"pairing_link\", r.link || \"\");",
                  "console.log(\"código:\", r.code, \"| abra no celular:\", r.link);"
                ]
              }
            }
          ]
        },
        {
          "name": "Vínculos do usuário",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/contacts/ext:{{external_id}}/links",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "ext:{{external_id}}",
                "links"
              ]
            },
            "description": "Os vínculos ativos do `external_id`, com a origem em `source`: `pairing` (pelo código) ou `routine` (número lembrado pelo roteiro, com a licença da pessoa). Sem vínculo (pareamento ainda não concluído pelo celular, ou depois de desvincular), a lista vem vazia. Guarda o primeiro em `link_id`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data || [];",
                  "if (d[0]) pm.environment.set(\"link_id\", d[0].id);",
                  "else console.log(\"Sem vínculo ativo para\", pm.variables.get(\"external_id\"), \"- abra o pairing_link no celular, mande a mensagem e confirme; depois rode de novo.\");"
                ]
              }
            }
          ]
        },
        {
          "name": "Desvincular",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/contacts/ext:{{external_id}}/links/{{link_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "ext:{{external_id}}",
                "links",
                "{{link_id}}"
              ]
            },
            "description": ""
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 204\", () => pm.response.to.have.status(204));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "6. Erros esperados",
      "description": "Cada requisição confere o código de erro que a API deve devolver.",
      "item": [
        {
          "name": "Corpo inválido (type)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"audio\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Arquivo com tipo errado (PDF como imagem)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"image\",\n  \"image\": {\n    \"url\": \"{{document_url}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));",
                  "pm.test(\"aponta a URL\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"image.url\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Pausa com minutes inválido",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/conversations/{{conversation_id}}/pause",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations",
                "{{conversation_id}}",
                "pause"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"minutes\": 0\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Campanha sem Idempotency-Key",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/campaigns",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "campaigns"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"kind\": \"marketing\",\n  \"template\": {\n    \"name\": \"x\"\n  },\n  \"audience\": {\n    \"tags\": [\n      \"vip\"\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));",
                  "pm.test(\"aponta o cabeçalho\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"Idempotency-Key\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Componente fora do formato (4 botões)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"x\",\n  \"components\": {\n    \"type\": \"buttons\",\n    \"buttons\": [\n      {\n        \"title\": \"Sim, quero\",\n        \"id\": \"quero_cardapio\"\n      },\n      {\n        \"title\": \"Agora não\",\n        \"id\": \"agora_nao\"\n      },\n      {\n        \"title\": \"Sim, quero\",\n        \"id\": \"quero_cardapio\"\n      },\n      {\n        \"title\": \"Agora não\",\n        \"id\": \"agora_nao\"\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));",
                  "pm.test(\"aponta o campo\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"components.buttons\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Botão com ação que não existe",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Precisa da janela de 24 horas aberta (a conferência das ações vem depois da janela).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"x\",\n  \"components\": {\n    \"type\": \"buttons\",\n    \"buttons\": [\n      {\n        \"title\": \"Teste\",\n        \"action\": {\n          \"name\": \"acao_que_nao_existe\",\n          \"params\": {}\n        }\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Pedido numa lista",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Ação que cria pedido só vai em buttons (precisa da ação `criar_pedido` no chatbot e da janela aberta).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"x\",\n  \"components\": {\n    \"type\": \"list\",\n    \"items\": [\n      {\n        \"title\": \"Pedir\",\n        \"action\": {\n          \"name\": \"criar_pedido\",\n          \"params\": {\n            \"itens\": [\n              \"101\"\n            ],\n            \"endereco\": \"Rua A, 1\"\n          }\n        }\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Etiquetas em contato que não existe",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/contacts/ctc_00000000-0000-4000-8000-000000000000/tags",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "ctc_00000000-0000-4000-8000-000000000000",
                "tags"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"tags\": [\n    \"vip\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 404\", () => pm.response.to.have.status(404));",
                  "pm.test(\"erro not_found\", () => pm.expect(pm.response.json().error.code).to.eql(\"not_found\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Aceite sem a prova",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/contacts/phone:{{phone}}/consent",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "phone:{{phone}}",
                "consent"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"granted\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Conversa inexistente",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/conversations/conv_00000000-0000-4000-8000-000000000000/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations",
                "conv_00000000-0000-4000-8000-000000000000",
                "messages"
              ]
            },
            "description": ""
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 404\", () => pm.response.to.have.status(404));",
                  "pm.test(\"erro not_found\", () => pm.expect(pm.response.json().error.code).to.eql(\"not_found\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Portão: regulamentado sem 18+",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Use em `phone_sem_idade` um número que escreveu nas últimas 24 horas e não confirmou 18+. Volta prohibited_content com reason age_not_confirmed e o termo.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone_sem_idade}}\",\n  \"type\": \"text\",\n  \"text\": \"Temos cerveja gelada hoje!\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 422\", () => pm.response.to.have.status(422));",
                  "pm.test(\"erro prohibited_content\", () => pm.expect(pm.response.json().error.code).to.eql(\"prohibited_content\"));",
                  "pm.test(\"motivo age_not_confirmed\", () => pm.expect(pm.response.json().error.details.reason).to.eql(\"age_not_confirmed\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Fora da janela de 24 h",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Use em `phone_fora_da_janela` um número que não escreveu nas últimas 24 horas.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone_fora_da_janela}}\",\n  \"type\": \"text\",\n  \"text\": \"Oi\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 422\", () => pm.response.to.have.status(422));",
                  "pm.test(\"erro outside_messaging_window\", () => pm.expect(pm.response.json().error.code).to.eql(\"outside_messaging_window\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Chave inválida",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/conversations",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conversations"
              ]
            },
            "description": "Sobrescreve a autenticação da coleção com uma chave que não existe.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "bv_live_esta_chave_nao_existe_000000",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 401\", () => pm.response.to.have.status(401));",
                  "pm.test(\"erro invalid_api_key\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_api_key\"));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "7. Exclusão (LGPD)",
      "description": "**Cuidado: apaga de verdade.** O pedido do titular pela API apaga as conversas, os arquivos, os leads e a ficha do contato, põe o número na lista de supressão e emite o webhook contact.deleted. Use um número de teste em `phone_para_apagar`.",
      "item": [
        {
          "name": "Apagar contato (pedido do titular)",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/contacts/phone:{{phone_para_apagar}}?bot_id={{bot_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "phone:{{phone_para_apagar}}"
              ],
              "query": [
                {
                  "key": "bot_id",
                  "value": "{{bot_id}}"
                }
              ]
            },
            "description": "Responde 202 com o `request_id` na hora; a exclusão roda logo depois. O pedido aparece em Segurança, no painel, como executado (origem API). Depois disso, mandar modelo para esse número volta 422 contact_suppressed."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 202\", () => pm.response.to.have.status(202));",
                  "console.log(\"pedido:\", pm.response.json().request_id);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "8. Campanhas e lembretes",
      "description": "POST /v1/campaigns (permissão **campaigns**), sempre com Idempotency-Key (a coleção gera uma por envio). Campanha de marketing só vai para quem aceitou novidades; lembrete usa modelo de utilidade. Os envios saem pelo mesmo motor do painel e aparecem na aba Campanhas do cliente.",
      "item": [
        {
          "name": "Campanha de marketing (por etiqueta)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/campaigns",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "campaigns"
              ]
            },
            "description": "Público: contatos com a etiqueta \"vip\" (use **5. Etiquetas do contato** antes) que aceitaram novidades. `{\"type\": \"first_name\", \"fallback\": \"cliente\"}` põe o primeiro nome do contato na variável. Ajuste `variables` ao modelo. Público sem ninguém apto: 422 empty_audience, com quem ficou de fora e por quê. Chatbot sem tamanho: 422 bot_without_size.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"kind\": \"marketing\",\n  \"name\": \"Teste Postman\",\n  \"template\": {\n    \"name\": \"{{marketing_template}}\",\n    \"language\": \"pt_BR\",\n    \"variables\": [\n      {\n        \"type\": \"first_name\",\n        \"fallback\": \"cliente\"\n      }\n    ]\n  },\n  \"audience\": {\n    \"tags\": [\n      \"vip\"\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"campaign_id\", r.campaign_id);",
                  "  console.log(\"campanha:\", r.campaign_id, r.status, \"contatos:\", r.estimated_contacts, \"fora:\", JSON.stringify(r.excluded));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Campanha agendada (pelo telefone)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/campaigns",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "campaigns"
              ]
            },
            "description": "Sai daqui a 10 minutos (status scheduled). `contacts` aceita phone:, ctc_ e ext:.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"kind\": \"marketing\",\n  \"template\": {\n    \"name\": \"{{marketing_template}}\",\n    \"language\": \"pt_BR\",\n    \"variables\": [\n      {\n        \"type\": \"first_name\",\n        \"fallback\": \"cliente\"\n      }\n    ]\n  },\n  \"audience\": {\n    \"contacts\": [\n      \"phone:{{phone}}\"\n    ]\n  },\n  \"send_at\": \"{{send_at_future}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// daqui a 10 minutos (o lembrete e a campanha agendada precisam de data no futuro)",
                  "pm.environment.set(\"send_at_future\", new Date(Date.now() + 10 * 60 * 1000).toISOString());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"campaign_id\", r.campaign_id);",
                  "  console.log(\"campanha:\", r.campaign_id, r.status, \"contatos:\", r.estimated_contacts, \"fora:\", JSON.stringify(r.excluded));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Lembretes (uma linha por envio)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/campaigns",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "campaigns"
              ]
            },
            "description": "Modelo de utilidade; cada linha sai na hora dela. `variables` precisa ter o número de variáveis do modelo. Contato que nunca conversou com o bot exige a declaração de lembretes do cliente (sem ela: 400 apontando utility_consent).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"kind\": \"utility_reminder\",\n  \"name\": \"Lembrete teste\",\n  \"template\": {\n    \"name\": \"{{reminder_template}}\",\n    \"language\": \"pt_BR\"\n  },\n  \"sends\": [\n    {\n      \"contact\": \"phone:{{phone}}\",\n      \"send_at\": \"{{send_at_future}}\",\n      \"variables\": [\n        \"{{template_variable_1}}\"\n      ]\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// daqui a 10 minutos (o lembrete e a campanha agendada precisam de data no futuro)",
                  "pm.environment.set(\"send_at_future\", new Date(Date.now() + 10 * 60 * 1000).toISOString());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"campaign_id\", r.campaign_id);",
                  "  console.log(\"campanha:\", r.campaign_id, r.status, \"contatos:\", r.estimated_contacts, \"fora:\", JSON.stringify(r.excluded));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Status da campanha",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/campaigns/{{campaign_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "campaigns",
                "{{campaign_id}}"
              ]
            },
            "description": "Status (scheduled, sending, paused, finished, canceled) e os totais: enviadas, entregues, lidas, respondidas, falhas, puladas por motivo e descadastros."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "const t = pm.response.json().totals; console.log(pm.response.json().status, JSON.stringify(t));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "9. Botões, lista, link e carrossel",
      "description": "POST /v1/messages com `components` (um por mensagem, junto de um texto): `buttons` (até 3, título até 20), `list` (até 10 itens, título até 24 e descrição até 72), `link` (título até 20, HTTPS) e `carousel` (1 a 10 cards; cada card com `image` ou `video` HTTPS, `title` até 80 e/ou `text` até 300, e `buttons` com 1 ou 2 de resposta ou um `link`, do mesmo tipo e na mesma quantidade em todos os cards). Sem `action`, o clique volta para a IA como mensagem com o título, e o `id` volta no webhook message.received (`interactive.id`). Com `action`, o clique chama a ação direto, sem a IA, e vale como confirmação; os parâmetros ficam no BoaVoz. Ação que cria pedido só vai em `buttons`. No carrossel do WhatsApp, o texto do card leva o título no começo e é cortado em 160 caracteres; 1 card vira mensagem com imagem. No WhatsApp e no Instagram, a pessoa precisa ter escrito nas últimas 24 horas.",
      "item": [
        {
          "name": "Botões no WhatsApp",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Toque num botão: a IA responde ao título, e o webhook message.received traz `interactive` com o id (`quero_cardapio`).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"Quer receber o cardápio do dia?\",\n  \"components\": {\n    \"type\": \"buttons\",\n    \"buttons\": [\n      {\n        \"title\": \"Sim, quero\",\n        \"id\": \"quero_cardapio\"\n      },\n      {\n        \"title\": \"Agora não\",\n        \"id\": \"agora_nao\"\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Botão com ação (adicionar ao carrinho)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Precisa da ação `adicionar_ao_carrinho` no chatbot (aba Ações). \"Pedir calabresa\" chama a ação direto, com `{\"item\": \"101\"}`; \"Ver cardápio\" volta para a IA.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"A calabresa saiu do forno agora!\",\n  \"components\": {\n    \"type\": \"buttons\",\n    \"buttons\": [\n      {\n        \"title\": \"Pedir calabresa\",\n        \"action\": {\n          \"name\": \"adicionar_ao_carrinho\",\n          \"params\": {\n            \"item\": \"101\"\n          }\n        }\n      },\n      {\n        \"title\": \"Ver cardápio\",\n        \"id\": \"ver_cardapio\"\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Lista no WhatsApp",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "No WhatsApp, a lista abre pelo botão \"Ver sabores\". No Instagram, vira respostas rápidas com o resumo numerado.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"Escolha o sabor:\",\n  \"components\": {\n    \"type\": \"list\",\n    \"button\": \"Ver sabores\",\n    \"items\": [\n      {\n        \"title\": \"Pizza Calabresa\",\n        \"description\": \"R$ 49,90\",\n        \"action\": {\n          \"name\": \"adicionar_ao_carrinho\",\n          \"params\": {\n            \"item\": \"101\"\n          }\n        }\n      },\n      {\n        \"title\": \"Pizza Marguerita\",\n        \"description\": \"R$ 45,90\",\n        \"action\": {\n          \"name\": \"adicionar_ao_carrinho\",\n          \"params\": {\n            \"item\": \"102\"\n          }\n        }\n      },\n      {\n        \"title\": \"Falar com a loja\",\n        \"id\": \"falar_loja\"\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Link no WhatsApp",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "No WhatsApp, botão que abre o link. No Instagram, o link vai no texto.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"Acompanhe o seu pedido pelo site.\",\n  \"components\": {\n    \"type\": \"link\",\n    \"title\": \"Ver pedido\",\n    \"url\": \"https://boavoz.com\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Carrossel no WhatsApp",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Cards que rolam para o lado, com foto e o botão \"Pedir este\". Nas pizzas, o botão chama `adicionar_ao_carrinho` direto (precisa da ação no chatbot); na Coca-Cola, o toque volta para a IA como \"Pedir este: Coca-Cola 2L\" e o webhook traz `interactive.id` = `pedir_coca`. Item proibido ou 18+ num card: 422 `prohibited_content` (a API nunca edita).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"Saíram do forno agora:\",\n  \"components\": {\n    \"type\": \"carousel\",\n    \"cards\": [\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Calabresa\",\n        \"title\": \"Pizza Calabresa\",\n        \"text\": \"R$ 49,90. Calabresa, cebola e azeitona.\",\n        \"buttons\": [\n          {\n            \"title\": \"Pedir este\",\n            \"action\": {\n              \"name\": \"adicionar_ao_carrinho\",\n              \"params\": {\n                \"item\": \"101\"\n              }\n            }\n          }\n        ]\n      },\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Marguerita\",\n        \"title\": \"Pizza Marguerita\",\n        \"text\": \"R$ 45,90. Tomate, muçarela e manjericão.\",\n        \"buttons\": [\n          {\n            \"title\": \"Pedir este\",\n            \"action\": {\n              \"name\": \"adicionar_ao_carrinho\",\n              \"params\": {\n                \"item\": \"102\"\n              }\n            }\n          }\n        ]\n      },\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Coca-Cola\",\n        \"title\": \"Coca-Cola 2L\",\n        \"text\": \"R$ 12,00.\",\n        \"buttons\": [\n          {\n            \"title\": \"Pedir este\",\n            \"id\": \"pedir_coca\"\n          }\n        ]\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Carrossel de links",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Um botão de link por card. No Instagram, os cards com o botão de link e, numa DM própria, os links no texto (para quem está no instagram.com pelo computador).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"Veja no site:\",\n  \"components\": {\n    \"type\": \"carousel\",\n    \"cards\": [\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Calabresa\",\n        \"title\": \"Pizza Calabresa\",\n        \"link\": {\n          \"title\": \"Ver no site\",\n          \"url\": \"https://boavoz.com\"\n        }\n      },\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Marguerita\",\n        \"title\": \"Pizza Marguerita\",\n        \"link\": {\n          \"title\": \"Ver no site\",\n          \"url\": \"https://boavoz.com\"\n        }\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Erro: carrossel com botões diferentes",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "400 `invalid_request` com `field` = `components.cards[1].buttons`: todos os cards precisam do mesmo tipo e da mesma quantidade de botões (regra do WhatsApp).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"phone:{{phone}}\",\n  \"type\": \"text\",\n  \"text\": \"Teste\",\n  \"components\": {\n    \"type\": \"carousel\",\n    \"cards\": [\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Calabresa\",\n        \"title\": \"Pizza Calabresa\",\n        \"text\": \"R$ 49,90. Calabresa, cebola e azeitona.\",\n        \"buttons\": [\n          {\n            \"title\": \"Pedir este\",\n            \"action\": {\n              \"name\": \"adicionar_ao_carrinho\",\n              \"params\": {\n                \"item\": \"101\"\n              }\n            }\n          }\n        ]\n      },\n      {\n        \"image\": \"https://placehold.co/800x600.png\",\n        \"title\": \"Outro\",\n        \"buttons\": [\n          {\n            \"title\": \"A\",\n            \"id\": \"a\"\n          },\n          {\n            \"title\": \"B\",\n            \"id\": \"b\"\n          }\n        ]\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));"
                ]
              }
            }
          ]
        },
        {
          "name": "Carrossel no Instagram",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "No app, os cards com foto e o botão \"Pedir este\" (generic template; o toque volta pelo webhook messaging_postbacks). Numa DM própria, o resumo numerado com as respostas rápidas, que é o que aparece no instagram.com pelo computador.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"{{instagram_contact}}\",\n  \"type\": \"text\",\n  \"text\": \"Saíram do forno agora:\",\n  \"components\": {\n    \"type\": \"carousel\",\n    \"cards\": [\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Calabresa\",\n        \"title\": \"Pizza Calabresa\",\n        \"text\": \"R$ 49,90. Calabresa, cebola e azeitona.\",\n        \"buttons\": [\n          {\n            \"title\": \"Pedir este\",\n            \"action\": {\n              \"name\": \"adicionar_ao_carrinho\",\n              \"params\": {\n                \"item\": \"101\"\n              }\n            }\n          }\n        ]\n      },\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Marguerita\",\n        \"title\": \"Pizza Marguerita\",\n        \"text\": \"R$ 45,90. Tomate, muçarela e manjericão.\",\n        \"buttons\": [\n          {\n            \"title\": \"Pedir este\",\n            \"action\": {\n              \"name\": \"adicionar_ao_carrinho\",\n              \"params\": {\n                \"item\": \"102\"\n              }\n            }\n          }\n        ]\n      },\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Coca-Cola\",\n        \"title\": \"Coca-Cola 2L\",\n        \"text\": \"R$ 12,00.\",\n        \"buttons\": [\n          {\n            \"title\": \"Pedir este\",\n            \"id\": \"pedir_coca\"\n          }\n        ]\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Botões no Instagram",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "O texto e, numa DM própria, o resumo numerado com as respostas rápidas (no instagram.com pelo computador, responda com o número).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bot_id\": \"{{bot_id}}\",\n  \"to\": \"{{instagram_contact}}\",\n  \"type\": \"text\",\n  \"text\": \"Quer receber o cardápio do dia?\",\n  \"components\": {\n    \"type\": \"buttons\",\n    \"buttons\": [\n      {\n        \"title\": \"Sim, quero\",\n        \"id\": \"quero_cardapio\"\n      },\n      {\n        \"title\": \"Agora não\",\n        \"id\": \"agora_nao\"\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 202\", () => pm.expect([201, 202]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Botões no chat do site",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Os botões aparecem embaixo do balão da Ana; só a última mensagem da conversa aceita toque.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"conversation_id\": \"{{widget_conversation_id}}\",\n  \"type\": \"text\",\n  \"text\": \"A calabresa saiu do forno agora!\",\n  \"agent_name\": \"Ana\",\n  \"components\": {\n    \"type\": \"buttons\",\n    \"buttons\": [\n      {\n        \"title\": \"Pedir calabresa\",\n        \"action\": {\n          \"name\": \"adicionar_ao_carrinho\",\n          \"params\": {\n            \"item\": \"101\"\n          }\n        }\n      },\n      {\n        \"title\": \"Ver cardápio\",\n        \"id\": \"ver_cardapio\"\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Carrossel no chat do site",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/messages",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Os cards aparecem embaixo do balão da Ana e rolam para o lado.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"conversation_id\": \"{{widget_conversation_id}}\",\n  \"type\": \"text\",\n  \"text\": \"Saíram do forno agora:\",\n  \"agent_name\": \"Ana\",\n  \"components\": {\n    \"type\": \"carousel\",\n    \"cards\": [\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Calabresa\",\n        \"title\": \"Pizza Calabresa\",\n        \"text\": \"R$ 49,90. Calabresa, cebola e azeitona.\",\n        \"buttons\": [\n          {\n            \"title\": \"Pedir este\",\n            \"action\": {\n              \"name\": \"adicionar_ao_carrinho\",\n              \"params\": {\n                \"item\": \"101\"\n              }\n            }\n          }\n        ]\n      },\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Marguerita\",\n        \"title\": \"Pizza Marguerita\",\n        \"text\": \"R$ 45,90. Tomate, muçarela e manjericão.\",\n        \"buttons\": [\n          {\n            \"title\": \"Pedir este\",\n            \"action\": {\n              \"name\": \"adicionar_ao_carrinho\",\n              \"params\": {\n                \"item\": \"102\"\n              }\n            }\n          }\n        ]\n      },\n      {\n        \"image\": \"https://placehold.co/800x600.png?text=Coca-Cola\",\n        \"title\": \"Coca-Cola 2L\",\n        \"text\": \"R$ 12,00.\",\n        \"buttons\": [\n          {\n            \"title\": \"Pedir este\",\n            \"id\": \"pedir_coca\"\n          }\n        ]\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201 || pm.response.code === 202) {",
                  "  const r = pm.response.json();",
                  "  pm.environment.set(\"message_id\", r.message_id);",
                  "  pm.test(\"status da mensagem: \" + r.status, () => pm.expect([\"sent\", \"uncertain\"]).to.include(r.status));",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "10. Cobrança",
      "description": "Uso, tamanho e excedente do chatbot e o crédito da conta. Precisam de uma chave com a permissão **billing** (só o dono da conta cria); o crédito pede também o escopo da conta inteira. Trocar para um tamanho maior e comprar crédito COBRAM no cartão da assinatura (na dev, o cartão de teste da Stripe).",
      "item": [
        {
          "name": "Uso do chatbot",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/usage",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "usage"
              ]
            },
            "description": "Tamanho, cota, uso do mês, projeção, média dos meses anteriores, tamanho sugerido, troca agendada e excedente. Guarda o tamanho em `bot_size`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 200) {",
                  "  const u = pm.response.json();",
                  "  pm.test(\"uso do mês\", () => { pm.expect(u).to.have.property(\"quota\"); pm.expect(u).to.have.property(\"used\"); pm.expect(u.overage).to.have.property(\"enabled\"); });",
                  "  pm.environment.set(\"bot_size\", u.size === null ? \"\" : u.size);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Excedente ligado com teto",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}"
              ]
            },
            "description": "Liga o excedente com teto de R$ 50 no mês (142 atendimentos a mais, a R$ 0,35). Só chatbot com tamanho de IA; nos outros, 422 overage_not_available. Sai o webhook bot.overage_changed.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"overage\": {\n    \"enabled\": true,\n    \"cap_brl\": 50\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"excedente ligado com teto de 50\", () => { const o = pm.response.json().overage; pm.expect(o.enabled).to.eql(true); pm.expect(o.cap_brl).to.eql(50); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Excedente desligado",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}"
              ]
            },
            "description": "Desliga o excedente (o teto fica guardado: sem cap_brl, ele não muda).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"overage\": {\n    \"enabled\": false\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"excedente desligado\", () => pm.expect(pm.response.json().overage.enabled).to.eql(false));"
                ]
              }
            }
          ]
        },
        {
          "name": "Mesmo tamanho (nada muda)",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}"
              ]
            },
            "description": "Manda o tamanho atual (guardado por \"Uso do chatbot\"): `size_change.status` volta `unchanged` e nada é cobrado. Para subir de verdade, troque por um tamanho maior: cobra a diferença proporcional até o dia 1º no cartão da assinatura (402 payment_required com a fatura, se o cartão recusar). Descer fica agendado para o dia 1º (status scheduled); mandar o tamanho atual de novo cancela o agendado.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"size\": \"{{bot_size}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"nada mudou\", () => pm.expect(pm.response.json().size_change.status).to.eql(\"unchanged\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Trocar tamanho sem Idempotency-Key",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}"
              ]
            },
            "description": "Trocar o tamanho pode cobrar: a Idempotency-Key é obrigatória.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"size\": \"G\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));",
                  "pm.test(\"aponta o cabeçalho\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"Idempotency-Key\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Tamanho que não existe",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"size\": \"XL\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));",
                  "pm.test(\"aponta o size\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"size\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Crédito da conta",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/credit",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "credit"
              ]
            },
            "description": "Saldo, lotes que ainda valem e a recarga automática. Pede chave com escopo da conta inteira (senão, 403 forbidden_scope)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"saldo\", () => pm.expect(pm.response.json()).to.have.property(\"balance_brl\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Comprar R$ 50 de crédito (cobra)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/credit/purchases",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "credit",
                "purchases"
              ]
            },
            "description": "COBRA R$ 50 no cartão da assinatura (na dev, o de teste da Stripe). 201 com o saldo novo; 402 payment_required com a fatura, se o cartão recusar. O crédito vale 12 meses e não é reembolsável (salvo na desistência dos 7 dias). Na primeira semana da assinatura, as compras somam até R$ 100: passou disso, 422 first_week_credit_limit.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount_brl\": 50\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 402 ou 422\", () => pm.expect([201, 402, 422]).to.include(pm.response.code));",
                  "if (pm.response.code === 201) pm.test(\"comprado\", () => pm.expect(pm.response.json().status).to.eql(\"paid\"));",
                  "if (pm.response.code === 422) pm.test(\"limite da primeira semana\", () => pm.expect(pm.response.json().error.code).to.eql(\"first_week_credit_limit\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Compra abaixo do mínimo",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/credit/purchases",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "credit",
                "purchases"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount_brl\": 10\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));",
                  "pm.test(\"aponta o valor\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"amount_brl\"));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "11. Fontes",
      "description": "Fontes de conhecimento pela API (permissão **sources**): texto, FAQ, planilha CSV, uma página ou um PDF por URL, e o aviso do momento (o texto com prazo que a IA usa quando perguntam; também com **provisioning**). Com `context`, a fonte só vale nas conversas daquele contexto (o do token, do pareamento ou o contexto fixo do chatbot) e só passa pelo portão, sem refazer a análise do bot. A leitura acontece na hora: a resposta já diz quantos trechos entraram.",
      "item": [
        {
          "name": "Fonte de texto com contexto",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/sources",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "sources"
              ]
            },
            "description": "Vale só nas conversas com `workspace_id` igual a `ws_abc` no contexto. Guarda o id em `source_id`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"text\",\n  \"title\": \"Regulamento do Condomínio Jardim\",\n  \"content\": \"Mudanças só de segunda a sábado, das 8h às 18h, com aviso de 48 horas à portaria. O salão de festas é reservado pelo aplicativo, com até 30 dias de antecedência.\",\n  \"context\": {\n    \"workspace_id\": \"ws_abc\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const s = pm.response.json();",
                  "  pm.environment.set(\"source_id\", s.id);",
                  "  pm.test(\"fonte lida\", () => pm.expect(s.status).to.eql(\"ready\"));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Fonte PDF por URL",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/sources",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "sources"
              ]
            },
            "description": "Sem `context`: vale em todas as conversas do chatbot (e a análise do bot roda de novo). O PDF vai até 15 MB; um PDF só de imagem volta 422 source_unreadable.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"pdf\",\n  \"url\": \"{{pdf_url}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));"
                ]
              }
            }
          ]
        },
        {
          "name": "Fonte planilha CSV",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/sources",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "sources"
              ]
            },
            "description": "O texto do arquivo em `content` (até 30 mil caracteres), com a primeira linha de cabeçalho; separador `;` ou `,`. Cada linha vira um trecho com o nome das colunas e uma linha da tabela que a IA consulta pelo nome ou código e filtra por número (ferramenta buscar_na_planilha). `columns` (opcional) diz o tipo de cada coluna: `texto`, `numero`, `preco`, `quantidade` ou `link`; a coluna que não vier ganha o tipo adivinhado pelo nome e pelos valores. A resposta traz `columns` com o tipo de todas.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"csv\",\n  \"title\": \"Tabela de preços\",\n  \"content\": \"Serviço;Preço;Duração\\nCorte masculino;R$ 45;30 min\\nBarba;R$ 35;20 min\\nCorte e barba;R$ 70;50 min\",\n  \"columns\": [\n    {\n      \"name\": \"Preço\",\n      \"type\": \"preco\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201\", () => pm.response.to.have.status(201));"
                ]
              }
            }
          ]
        },
        {
          "name": "Fontes de um contexto",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/sources?context={\"workspace_id\":\"ws_abc\"}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "sources"
              ],
              "query": [
                {
                  "key": "context",
                  "value": "{\"workspace_id\":\"ws_abc\"}"
                },
                {
                  "key": "tagged",
                  "value": "false",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "disabled": true
                }
              ]
            },
            "description": "Só as fontes com essa etiqueta. Sem `context`, todas (as mais novas primeiro); `tagged=false` lista só as gerais."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"lista\", () => pm.expect(pm.response.json().data).to.be.an(\"array\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Apagar fonte",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/sources/{{source_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "sources",
                "{{source_id}}"
              ]
            },
            "description": "Tira a fonte e os trechos dela. Responde 204."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 204\", () => pm.response.to.have.status(204));"
                ]
              }
            }
          ]
        },
        {
          "name": "Fonte sem conteúdo",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/sources",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "sources"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"text\",\n  \"content\": \"curto\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));",
                  "pm.test(\"aponta o campo\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"content\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Pôr o aviso do momento",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/notice",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "notice"
              ]
            },
            "description": "O texto com prazo que a IA usa quando perguntam (o mesmo do painel). `until` em ISO 8601 com o fuso, de 10 minutos a 30 dias à frente; o pré-script põe daqui a 6 horas. Permissão sources ou provisioning.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"text\": \"Instabilidade na fibra no bairro Centro; previsão de volta às 18h.\",\n  \"until\": \"{{notice_until}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.collectionVariables.set(\"notice_until\", new Date(Date.now() + 6 * 3600 * 1000).toISOString());"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"o aviso no ar\", () => pm.expect(pm.response.json().notice.text).to.include(\"Centro\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Aviso do momento",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/notice",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "notice"
              ]
            },
            "description": "O aviso que vale agora, ou `notice: null` (sem aviso ou vencido)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Tirar o aviso do momento",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/notice",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "notice"
              ]
            },
            "description": "Tira antes do prazo. `removed` diz se havia aviso valendo."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"sem aviso\", () => pm.expect(pm.response.json().notice).to.eql(null));"
                ]
              }
            }
          ]
        },
        {
          "name": "Aviso com prazo longo demais",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/notice",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "notice"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"text\": \"Manutenção programada da rede.\",\n  \"until\": \"2099-01-01T00:00:00Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));",
                  "pm.test(\"aponta o campo\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"until\"));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "12. Lojas (provisionamento)",
      "description": "Clientes (as lojas) e os chatbots deles a partir de um modelo, pelo seu sistema (permissão **provisioning**, pacote **Plataforma**). Criar cliente pede uma chave com escopo da conta inteira. Preencha `template_id` no ambiente com o id do modelo (aba Herança do modelo, no painel). Rodar de novo não cria outro: o `external_id` devolve o que já existe, com 200.",
      "item": [
        {
          "name": "Criar cliente (loja)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/clients",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "clients"
              ]
            },
            "description": "201 na primeira vez; 200 com o mesmo cliente quando o `external_id` já existe na conta. Guarda o id em `client_id`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Pizzaria do Zé\",\n  \"external_id\": \"{{store_external_id}}\",\n  \"site\": \"pizzariadoze.com.br\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 200\", () => pm.expect([201, 200]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 200) {",
                  "  const c = pm.response.json();",
                  "  pm.environment.set(\"client_id\", c.client_id);",
                  "  pm.test(\"cliente ativo\", () => pm.expect(c.compliance_status).to.eql(\"active\"));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Criar cliente de novo (mesmo external_id)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/clients",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "clients"
              ]
            },
            "description": "O mesmo `external_id`, sem Idempotency-Key (com a mesma chave, a resposta guardada voltaria igual): devolve o mesmo cliente (200), sem criar outro e sem mudar nada.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Pizzaria do Zé\",\n  \"external_id\": \"{{store_external_id}}\",\n  \"site\": \"pizzariadoze.com.br\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"o mesmo cliente\", () => pm.expect(pm.response.json().client_id).to.eql(pm.variables.get(\"client_id\")));"
                ]
              }
            }
          ]
        },
        {
          "name": "Cliente com a declaração de lembretes",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/clients",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "clients"
              ]
            },
            "description": "A declaração de consentimento para lembretes (a mesma do painel, aba Campanhas do cliente), feita junto. A chave fica como autora.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Clínica Sorriso\",\n  \"external_id\": \"{{store_external_id}}-lembretes\",\n  \"utility_consent\": {\n    \"source\": \"contrato\",\n    \"text\": \"Cláusula 7 do contrato: o paciente aceita lembretes de consulta pelo WhatsApp.\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 200\", () => pm.expect([201, 200]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "Ler cliente",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/clients/{{client_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "clients",
                "{{client_id}}"
              ]
            },
            "description": "Os dados, os chatbots que a chave alcança (com os canais da Meta conectados) e a conformidade, com as restrições em vigor."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"chatbots\", () => pm.expect(pm.response.json().bots).to.be.an(\"array\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Criar chatbot do modelo",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots"
              ]
            },
            "description": "O chatbot da loja, com o que o modelo tem (personalidade, instruções, ações, roteiros e base). O `context` vai para as ações e escolhe as fontes da loja; o `display` é o que a IA sabe dela. Nasce no ar quando o modelo tem fonte pronta (senão, em rascunho) e sem tamanho, como no painel. Guarda o id em `provisioned_bot_id`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"client_id\": \"{{client_id}}\",\n  \"template_id\": \"{{template_id}}\",\n  \"external_id\": \"{{store_external_id}}-site\",\n  \"context\": {\n    \"loja_id\": \"42\"\n  },\n  \"display\": {\n    \"nome\": \"Pizzaria do Zé, Centro\",\n    \"horario\": \"terça a domingo, das 18h às 23h\",\n    \"endereco\": \"Rua das Flores, 120\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 200\", () => pm.expect([201, 200]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 200) {",
                  "  const b = pm.response.json();",
                  "  pm.environment.set(\"provisioned_bot_id\", b.bot_id);",
                  "  pm.test(\"chatbot do modelo\", () => pm.expect(b.template_id).to.eql(pm.variables.get(\"template_id\")));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Chatbot com valores próprios",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots"
              ]
            },
            "description": "Só nos campos que o modelo libera (aba Herança): campo travado volta 403 field_locked.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"client_id\": \"{{client_id}}\",\n  \"template_id\": \"{{template_id}}\",\n  \"external_id\": \"{{store_external_id}}-delivery\",\n  \"context\": {\n    \"loja_id\": \"42\"\n  },\n  \"display\": {\n    \"nome\": \"Pizzaria do Zé, delivery\"\n  },\n  \"overrides\": {\n    \"assistant_name\": \"Bia\",\n    \"welcome\": \"Oi! Sou a Bia, do delivery do Zé. Quer ver o cardápio?\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 200 ou 403\", () => pm.expect([201, 200, 403]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "Link de conexão do WhatsApp",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/connect-links",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "connect-links"
              ]
            },
            "description": "O link aberto do chatbot no WhatsApp, ou um novo (vale 7 dias, uma conexão). Pedir de novo devolve o mesmo; só `rotate: true` troca. Com o canal já conectado, o link serve para conectar de novo (o número novo substitui o antigo). Chatbot sem tamanho: 422 bot_without_size. Guarda a URL em `connect_url`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"channel\": \"whatsapp\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200 ou 422\", () => pm.expect([200, 422]).to.include(pm.response.code));",
                  "if (pm.response.code === 200) {",
                  "  const l = pm.response.json();",
                  "  pm.environment.set(\"connect_url\", l.url);",
                  "  pm.test(\"link aberto\", () => pm.expect(l.url).to.include(\"/conectar/\"));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Link de novo (o mesmo)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/connect-links",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "connect-links"
              ]
            },
            "description": "Enquanto o link estiver aberto, a resposta é a mesma URL.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"channel\": \"whatsapp\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200 ou 422\", () => pm.expect([200, 422]).to.include(pm.response.code));",
                  "if (pm.response.code === 200) pm.test(\"o mesmo link\", () => pm.expect(pm.response.json().url).to.eql(pm.variables.get(\"connect_url\")));"
                ]
              }
            }
          ]
        },
        {
          "name": "Canais do chatbot",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/channels",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "channels"
              ]
            },
            "description": "Os canais da Meta do chatbot, com o estado (conectado ou caído, com o motivo) e o problema em aberto (a conta do WhatsApp sem forma de pagamento). O `id` é o mesmo dos webhooks channel.*."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"lista\", () => pm.expect(pm.response.json().data).to.be.an(\"array\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Desconectar canal (de vez)",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/channels/{{channel_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "channels",
                "{{channel_id}}"
              ]
            },
            "description": "CUIDADO: desconecta de verdade o canal `channel_id` (preencha com um id de \"Canais do chatbot\"). Tira a inscrição e apaga o token; o número continua do negócio e nunca é descadastrado. Até 5 por hora por chave, e o dono recebe um alerta de segurança. Sem `channel_id`, volta 404."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 204 ou 404\", () => pm.expect([204, 404]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "Desconectar canal que não existe",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/bots/{{bot_id}}/channels/chn_wa_0",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{bot_id}}",
                "channels",
                "chn_wa_0"
              ]
            },
            "description": ""
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 404\", () => pm.response.to.have.status(404));",
                  "pm.test(\"erro not_found\", () => pm.expect(pm.response.json().error.code).to.eql(\"not_found\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Pausar chatbot",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{provisioned_bot_id}}/pause",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{provisioned_bot_id}}",
                "pause"
              ]
            },
            "description": "A pausa do dono (a mesma do painel): a IA para em todos os canais, os canais seguem conectados e as mensagens ficam para a equipe. Com `notify_contacts`, o contato recebe o aviso de indisponível uma vez por conversa. Sai o webhook bot.paused.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"loja fechada para reforma\",\n  \"notify_contacts\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"pausado\", () => pm.expect(pm.response.json().paused).to.eql(true));"
                ]
              }
            }
          ]
        },
        {
          "name": "Pausar de novo (o mesmo paused_at)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{provisioned_bot_id}}/pause",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{provisioned_bot_id}}",
                "pause"
              ]
            },
            "description": "Já pausado: devolve o paused_at da pausa que vale, com `already_paused: true`.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"já estava\", () => pm.expect(pm.response.json().already_paused).to.eql(true));"
                ]
              }
            }
          ]
        },
        {
          "name": "Retomar chatbot",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{provisioned_bot_id}}/resume",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{provisioned_bot_id}}",
                "resume"
              ]
            },
            "description": "Tira só a pausa do dono. Medidas do BoaVoz e ordens da Meta continuam e voltam em `restrictions`. Sai o webhook bot.resumed."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"retomado\", () => pm.expect(pm.response.json().paused).to.eql(false));"
                ]
              }
            }
          ]
        },
        {
          "name": "Convidar pessoa ao portal",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/clients/{{client_id}}/members",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "clients",
                "{{client_id}}",
                "members"
              ]
            },
            "description": "Pede a permissão **members** na chave (desmarcada por padrão). Dá acesso ao portal do cliente e manda o link por e-mail, com a marca da agência: preencha `member_email` com um e-mail seu. `role`: manager (gestor) ou agent (atendente, só as conversas). O mesmo e-mail devolve a pessoa (200). A chave fica registrada como quem convidou. Guarda o id em `member_id`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"{{member_email}}\",\n  \"role\": \"agent\",\n  \"display_name\": \"Carla\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 201 ou 200\", () => pm.expect([201, 200]).to.include(pm.response.code));",
                  "if (pm.response.code === 201 || pm.response.code === 200) {",
                  "  const m = pm.response.json();",
                  "  pm.environment.set(\"member_id\", m.id);",
                  "  pm.test(\"convidada pela chave\", () => pm.expect(m.invited_by && m.invited_by.type).to.eql(\"api_key\"));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Equipe do cliente",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/clients/{{client_id}}/members",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "clients",
                "{{client_id}}",
                "members"
              ]
            },
            "description": "As pessoas com acesso ao portal do cliente, com a chave que convidou cada uma (null: convite pelo painel)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"lista\", () => pm.expect(pm.response.json().data).to.be.an(\"array\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Remover pessoa do portal",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/clients/{{client_id}}/members/{{member_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "clients",
                "{{client_id}}",
                "members",
                "{{member_id}}"
              ]
            },
            "description": "Tira o acesso na hora: a sessão aberta perde o acesso na próxima página. Responde 204."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 204\", () => pm.response.to.have.status(204));"
                ]
              }
            }
          ]
        },
        {
          "name": "Mudar os dados do chatbot",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{provisioned_bot_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{provisioned_bot_id}}"
              ]
            },
            "description": "Permissão provisioning. O `display`, os valores próprios nos campos que o modelo libera (null volta ao modelo; campo travado: 403 field_locked), o horário do atendimento humano (sun a sat, horário de Brasília; null tira) e o destino do atendimento. A resposta é o chatbot. size e overage (permissão billing) vão em outro pedido.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"display\": {\n    \"nome\": \"Pizzaria do Zé, Centro\",\n    \"horario\": \"terça a domingo, das 18h às 23h\"\n  },\n  \"overrides\": {\n    \"welcome\": \"Oi! Sou a Duda, do Zé. Quer ver o cardápio de hoje?\"\n  },\n  \"business_hours\": {\n    \"sun\": [\n      \"18:00\",\n      \"23:00\"\n    ],\n    \"tue\": [\n      \"18:00\",\n      \"23:00\"\n    ],\n    \"wed\": [\n      \"18:00\",\n      \"23:00\"\n    ],\n    \"thu\": [\n      \"18:00\",\n      \"23:00\"\n    ],\n    \"fri\": [\n      \"18:00\",\n      \"23:30\"\n    ],\n    \"sat\": [\n      \"18:00\",\n      \"23:30\"\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200 ou 403\", () => pm.expect([200, 403]).to.include(pm.response.code));",
                  "if (pm.response.code === 200) pm.test(\"display salvo\", () => pm.expect(pm.response.json().display.nome).to.eql(\"Pizzaria do Zé, Centro\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Saudação de volta ao modelo",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{provisioned_bot_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{provisioned_bot_id}}"
              ]
            },
            "description": "null tira o valor próprio: o chatbot volta a usar a saudação do modelo.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"overrides\": {\n    \"welcome\": null\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200 ou 403\", () => pm.expect([200, 403]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "Destino do atendimento (avisa o dono)",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{provisioned_bot_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{provisioned_bot_id}}"
              ]
            },
            "description": "Os outros jeitos de falar com a equipe, que o assistente oferece quando a pessoa pede um atendente. Toda troca pela API manda um alerta de segurança ao dono da conta.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"handoff_destination\": {\n    \"phone\": \"(11) 3333-4444\",\n    \"email\": null\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Cobrança e chatbot juntos",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots/{{provisioned_bot_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots",
                "{{provisioned_bot_id}}"
              ]
            },
            "description": "Um ou outro por pedido: as respostas são diferentes.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"overage\": {\n    \"enabled\": false\n  },\n  \"display\": {\n    \"nome\": \"x\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Uso do mês",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/usage",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "usage"
              ],
              "query": [
                {
                  "key": "month",
                  "value": "2026-10",
                  "disabled": true
                },
                {
                  "key": "client_id",
                  "value": "{{client_id}}",
                  "disabled": true
                },
                {
                  "key": "bot_id",
                  "value": "{{provisioned_bot_id}}",
                  "disabled": true
                }
              ]
            },
            "description": "Por cliente e chatbot do escopo da chave: atendimentos, mensagens, contatos em campanhas e o custo estimado da Meta (sem o custo de IA). Com chave da conta inteira e sem filtro, também `account` (o excedente do mês e o saldo de crédito). Sem `month`, o mês atual."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"clientes\", () => pm.expect(pm.response.json().clients).to.be.an(\"array\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Chatbot sem Idempotency-Key",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots"
              ]
            },
            "description": "Criar chatbot pede a Idempotency-Key.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"client_id\": \"{{client_id}}\",\n  \"template_id\": \"{{template_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));",
                  "pm.test(\"aponta o cabeçalho\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"Idempotency-Key\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Modelo que não existe",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/bots",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bots"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"client_id\": \"{{client_id}}\",\n  \"template_id\": \"bot_00000000-0000-4000-8000-000000000000\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 404\", () => pm.response.to.have.status(404));",
                  "pm.test(\"erro not_found\", () => pm.expect(pm.response.json().error.code).to.eql(\"not_found\"));",
                  "pm.test(\"aponta o modelo\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"template_id\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Cliente sem external_id",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Uma chave nova a cada envio (o Postman gera). Repetir a mesma chave devolve a mesma resposta."
              }
            ],
            "url": {
              "raw": "{{base_url}}/clients",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "clients"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Pizzaria do Zé\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status 400\", () => pm.response.to.have.status(400));",
                  "pm.test(\"erro invalid_request\", () => pm.expect(pm.response.json().error.code).to.eql(\"invalid_request\"));",
                  "pm.test(\"aponta o campo\", () => pm.expect(pm.response.json().error.details.field).to.eql(\"external_id\"));"
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "template_name",
      "value": "",
      "type": "string"
    },
    {
      "key": "template_language",
      "value": "pt_BR",
      "type": "string"
    },
    {
      "key": "template_variable_1",
      "value": "Ruan",
      "type": "string"
    },
    {
      "key": "marketing_template",
      "value": "",
      "type": "string"
    },
    {
      "key": "reminder_template",
      "value": "",
      "type": "string"
    },
    {
      "key": "send_at_future",
      "value": "",
      "type": "string"
    },
    {
      "key": "campaign_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "external_id",
      "value": "user_postman_1",
      "type": "string"
    },
    {
      "key": "idempotency_key",
      "value": "teste-idempotencia-1",
      "type": "string"
    },
    {
      "key": "conversation_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "whatsapp_conversation_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "instagram_conversation_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "widget_conversation_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "whatsapp_contact",
      "value": "",
      "type": "string"
    },
    {
      "key": "instagram_contact",
      "value": "",
      "type": "string"
    },
    {
      "key": "message_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "next_cursor",
      "value": "",
      "type": "string"
    },
    {
      "key": "link_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "image_url",
      "value": "https://placehold.co/600x400.png",
      "type": "string"
    },
    {
      "key": "document_url",
      "value": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
      "type": "string"
    },
    {
      "key": "media_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "pairing_code",
      "value": "",
      "type": "string"
    },
    {
      "key": "pairing_link",
      "value": "",
      "type": "string"
    },
    {
      "key": "bot_size",
      "value": "",
      "type": "string"
    },
    {
      "key": "source_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "pdf_url",
      "value": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
      "type": "string"
    },
    {
      "key": "store_external_id",
      "value": "loja-postman-1",
      "type": "string"
    },
    {
      "key": "client_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "provisioned_bot_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "connect_url",
      "value": "",
      "type": "string"
    },
    {
      "key": "channel_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "member_id",
      "value": "",
      "type": "string"
    }
  ]
}
