# Editar fluxo

PATCH /chatbot/flows

Altera o rascunho: só os campos enviados mudam. `groups` vem inteiro e substitui todos os grupos (não junta); o documento vai até 256 KB. Sem controle de versão: a última gravação vence. Quem está rodando não muda nada até o publish.

## Autenticação

```json
[
  {
    "token": []
  }
]
```

```json
{
  "token": {
    "name": "token",
    "type": "apiKey",
    "in": "header"
  }
}
```

## Corpo da requisição

```json
{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/FlowInput"
      },
      "example": {
        "flow_id": "rw1b2c3d4e5f6a7",
        "name": "Recepção"
      }
    }
  }
}
```

## Respostas

```json
{
  "200": {
    "description": "Fluxo atualizado",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Flow"
        },
        "example": {
          "id": "rw1b2c3d4e5f6a7",
          "name": "Boas-vindas",
          "start_group_id": "g1",
          "cancel_on_reply": true,
          "version": 3,
          "published_at": "2026-10-17T12:00:00.000Z",
          "has_unpublished_changes": false,
          "webhook_key": "",
          "created_at": "2026-10-17T12:00:00.000Z",
          "updated_at": "2026-10-17T12:00:00.000Z",
          "groups": [
            {
              "id": "g1",
              "name": "Início",
              "position": {
                "x": 0,
                "y": 0
              },
              "blocks": [
                {
                  "id": "b1",
                  "type": "send",
                  "message": {
                    "type": "text",
                    "text": "Oi, {{name}}! Como posso ajudar?"
                  }
                },
                {
                  "id": "b2",
                  "type": "ask",
                  "text": "Escolha uma opção",
                  "menu": "button",
                  "options": [
                    {
                      "id": "o1",
                      "label": "Pedidos",
                      "next": "g2"
                    },
                    {
                      "id": "o2",
                      "label": "Falar com alguém",
                      "next": "g3"
                    }
                  ],
                  "save_to": "opcao"
                }
              ]
            }
          ],
          "send_window": null
        }
      }
    }
  },
  "400": {
    "description": "`invalid_request`: JSON malformado ou campo desconhecido; ou `name` vazio, id de grupo ou bloco repetido, tipo de bloco desconhecido, `send_window` inválido ou documento acima de 256 KB.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/OrkesioError"
        },
        "example": {
          "error": "invalid_request",
          "message": "JSON malformado ou campo desconhecido."
        }
      }
    }
  },
  "401": {
    "description": "Token ausente ou inválido.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/OrkesioError"
        },
        "example": {
          "error": "unauthorized",
          "message": "Token inválido."
        }
      }
    }
  },
  "404": {
    "description": "Nenhum fluxo com esse `flow_id` nesta inbox.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/OrkesioError"
        },
        "example": {
          "error": "not_found",
          "message": "Nenhum recurso com esse `id` nesta inbox."
        }
      }
    }
  },
  "409": {
    "description": "A inbox ainda não foi conectada ao WhatsApp. Os dados pertencem ao número conectado; conecte a inbox antes (`inbox_not_paired`).",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/OrkesioError"
        },
        "example": {
          "error": "inbox_not_paired",
          "message": "A inbox ainda não foi conectada ao WhatsApp. Os dados pertencem ao número conectado; conecte a inbox antes (`inbox_not_paired`)."
        }
      }
    }
  },
  "429": {
    "$ref": "#/components/responses/RateLimited"
  },
  "500": {
    "description": "Falha inesperada ao gravar ou ler os dados.",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/OrkesioError"
        },
        "example": {
          "error": "internal_error",
          "message": "Erro interno; tente de novo."
        }
      }
    }
  }
}
```

**Estado:** Disponível

## Compatibilidade de canais

| Canal | Compatível | Observação |
|---|---|---|
| WhatsApp (Web) | Sim |  |
| WhatsApp (API oficial) | Sim |  |
| Instagram | Sim |  |
| Messenger | Sim |  |
| Telegram | Sim |  |

## Eventos que esta rota gera

Nenhum. Esta rota não gera evento no webhook (nem em `GET /events`): o resultado vem só na resposta.

## Antes e depois (uazapi → Orkesio)

Na uazapi: `PATCH /chatbot/flows/{id}`.

- Mesmas funções; o id vai no corpo, em `flow_id` (antes `{id}` no caminho); muda o formato: campos em `snake_case`, datas em ISO 8601 (`_at`) e erros em `{error, message}`.
- Campos renomeados: `cancelOnReply` → `cancel_on_reply`, `createdAt` → `created_at`, `hasUnpublishedChanges` → `has_unpublished_changes`, `publishedAt` → `published_at`, `sendWindow` → `send_window`, `timeZone` → `time_zone`, `updatedAt` → `updated_at`, `webhookKey` → `webhook_key`.

## #/components/schemas/FlowInput

```json
{
  "type": "object",
  "additionalProperties": false,
  "description": "Só os campos enviados mudam. O rascunho é salvo mesmo incompleto (só forma e tamanho são conferidos): a validação completa é no publish.",
  "properties": {
    "flow_id": {
      "type": "string",
      "description": "ID do fluxo (`id` em `GET /chatbot/flows`)."
    },
    "name": {
      "type": "string",
      "description": "Nome do fluxo, não vazio."
    },
    "start_group_id": {
      "type": "string",
      "description": "Grupo onde o fluxo começa (`id` de um dos `groups`)."
    },
    "groups": {
      "type": "array",
      "maxItems": 200,
      "items": {
        "$ref": "#/components/schemas/FlowGroup"
      },
      "description": "Todos os grupos do rascunho: substitui os que havia (não junta). Até 200, com até 50 blocos cada."
    },
    "cancel_on_reply": {
      "type": "boolean",
      "description": "Mensagem do contato cancela a execução que aguarda (bloco `wait` ou horário de envio) e os agendamentos deste fluxo para ele. A mensagem segue para os gatilhos."
    },
    "send_window": {
      "oneOf": [
        {
          "$ref": "#/components/schemas/FlowSendWindow"
        },
        {
          "type": "null"
        }
      ],
      "description": "Horário de envio; `null` tira."
    }
  },
  "required": [
    "flow_id"
  ]
}
```

## #/components/schemas/FlowGroup

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "name",
    "position",
    "blocks"
  ],
  "description": "Grupo de blocos que rodam em ordem. Ao acabar, segue para `next`; sem `next`, o fluxo termina.",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{1,64}$",
      "description": "ID do grupo, único no fluxo."
    },
    "name": {
      "type": "string",
      "description": "Nome do grupo no editor."
    },
    "position": {
      "$ref": "#/components/schemas/FlowPosition"
    },
    "blocks": {
      "type": "array",
      "maxItems": 50,
      "items": {
        "$ref": "#/components/schemas/FlowBlock"
      }
    },
    "next": {
      "type": "string",
      "description": "Grupo seguinte."
    }
  }
}
```

## #/components/schemas/FlowPosition

```json
{
  "type": "object",
  "description": "Posição do grupo no editor.",
  "required": [
    "x",
    "y"
  ],
  "properties": {
    "x": {
      "type": "number"
    },
    "y": {
      "type": "number"
    }
  }
}
```

## #/components/schemas/FlowBlock

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "type"
  ],
  "description": "Bloco do fluxo. Cada tipo usa os seus campos.\n\n| type | Campos | O que faz |\n|---|---|---|\n| `send` | `message`, `to` | Envia a mensagem (`FlowMessage`: texto, mídia por URL, localização, contato ou chave PIX; botões, lista e enquete ficam no `ask`). Texto aceita `{{variável}}`, `{{phone}}` (número do contato) e os placeholders do lead. Com `to`, vai para outro número. |\n| `ask` | `text`, `menu`, `options`, `save_to`, `list_button`, `seconds`, `timeout_next` | Pergunta em texto (sem `menu`) ou menu (`button` até 3 opções, `list` até 10, `poll` até 12) e espera a resposta. A opção escolhida leva ao `next` dela; resposta sem opção segue para o bloco seguinte do grupo. `save_to` guarda a resposta numa variável. Com `seconds` (tempo limite), sem resposta nesse tempo o fluxo segue para `timeout_next` (sem ele, acaba como `expired`); sem `seconds`, a pergunta espera até 24 h. |\n| `request` | `request`, `error_next` | Chama uma API externa (só endereço público) e guarda campos da resposta em variáveis (`request.save`). Resposta fora de 2xx ou falha de rede leva a `error_next`; sem ele, segue para o bloco seguinte. Use `POST /chatbot/flows/test-request` para ver o que a API devolve. |\n| `condition` | `conditions`, `match`, `next` | Confere as regras (`match: all`, o padrão, pede todas; `any`, alguma). Verdadeira leva a `next`; senão, segue para o bloco seguinte. |\n| `set` | `save_to`, `value`, `operations` | Guarda em `save_to` o `value` (vazio é o valor atual da variável) depois das `operations`, em ordem. |\n| `clear` | `variables` | Apaga as variáveis listadas; sem `variables`, apaga todas. |\n| `set_contact` | `to`, `error_next` | Escolhe com quem o fluxo conversa daqui em diante (`{{variável}}` vale). Serve para o fluxo começado por POST sem `to`. Número que não é WhatsApp leva a `error_next`; sem ele, o fluxo para com erro. |\n| `wait` | `seconds` | Aguarda (1 s a 30 dias) e segue no bloco seguinte. Mensagem do contato durante a espera vai para os gatilhos (a IA pode responder); com `cancel_on_reply` no fluxo, ela também encerra a espera. A pausa do chatbot encerra a espera. |\n| `schedule_flow` | `flow_id`, `seconds` | Agenda o fluxo `flow_id` (publicado) para o mesmo contato daqui a `seconds` (até 30 dias), com as variáveis de agora, e segue. Na hora, ele começa como um gatilho, no lugar da execução que a conversa tiver. |\n| `go_to_flow` | `flow_id` | Segue agora na versão publicada de `flow_id`, com as variáveis e o contato; as respostas seguintes já são desse fluxo. |\n| `lead` | `fields`, `column` | Grava campos do lead (`{{variável}}` vale) e move o lead para a coluna do kanban `column`, como o `PATCH /leads`. Falha (conversa ou coluna que não existe) fica no log e o fluxo segue. |\n| `chatbot` | `action`, `seconds` | `pause`: pausa a IA, os gatilhos e os fluxos da conversa por `seconds` (0 = até alguém religar) e encerra o fluxo. `resume`: religa e segue (funciona mesmo com a conversa pausada). |\n| `human` | `text` | Chama um humano: pausa a conversa até alguém religar, grava `text` como motivo no `chatbot_summary` e encerra o fluxo. |\n| `ai` | `agent_id` | Passa a conversa para a IA: o fluxo acaba (`ai`) e a mensagem do contato que o trouxe até aqui (a do gatilho ou a resposta) vai para o agente agora. Sem ela (fluxo começado por POST, agendado ou depois de um `wait`), a próxima mensagem segue os gatilhos. |\n| `ai_message` | `agent_id`, `text` | Mensagem escrita pelo agente: ele lê o histórico da conversa e a instrução `text` (`{{variável}}` vale) e escreve; sai como um `send` e o fluxo segue. Menus, arquivos e `chamar_humano` não rodam aqui. |\n| `end` | — | Encerra o fluxo. |\n\nMensagem, pergunta e os blocos `lead`, `chatbot`, `human` e `ai_message` sem contato (fluxo começado por POST sem `to`, antes de um `set_contact`) são erro no log. Com a conversa pausada (alguém atende) ou o chatbot da inbox desligado, o fluxo roda os blocos que não falam com o contato e para na primeira mensagem ou pergunta; um `chatbot` com `resume` antes dela destrava a conversa pausada. Uma automação por vez: a conversa é de quem espera a resposta. Quando o fluxo faz uma pergunta, a resposta que a IA preparava para a conversa não sai; mensagem sem pergunta não cala a IA. Variável do fluxo que não existe sai vazia nas mensagens. Todo bloco aceita `note`. Todo bloco (menos `end`) aceita o anti-loop (`limit`, `limit_next`); os que enviam, `typing_seconds`. O limite conta também entre execuções do fluxo: o fluxo que recomeça a cada mensagem (robô respondendo robô) para no limite. O publish recusa ciclo sem pergunta (`loop_without_question`): grupos que voltam para si pelo \"depois\" sem esperar o contato.",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{1,64}$",
      "description": "ID do bloco, único no fluxo."
    },
    "type": {
      "type": "string",
      "enum": [
        "send",
        "ask",
        "request",
        "condition",
        "set",
        "clear",
        "set_contact",
        "wait",
        "schedule_flow",
        "go_to_flow",
        "lead",
        "chatbot",
        "human",
        "ai",
        "ai_message",
        "end"
      ]
    },
    "message": {
      "$ref": "#/components/schemas/FlowMessage"
    },
    "text": {
      "type": "string",
      "description": "Pergunta do bloco `ask`. No `human`, o motivo de chamar um humano (vai para o `chatbot_summary` da conversa; `{{variável}}` vale). No `ai_message`, a instrução do que a mensagem deve dizer."
    },
    "menu": {
      "type": "string",
      "enum": [
        "button",
        "list",
        "poll"
      ],
      "description": "Menu com as `options`: `button`, `list` ou `poll`. Sem `menu`, a pergunta é em texto."
    },
    "list_button": {
      "type": "string",
      "description": "Texto do botão que abre a lista (`menu: list`). Padrão: \"Ver opções\"."
    },
    "options": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/FlowOption"
      }
    },
    "save_to": {
      "type": "string",
      "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,39}$",
      "description": "Variável que guarda a resposta (`ask`: o rótulo da opção escolhida, ou o texto) ou o resultado (`set`)."
    },
    "timeout_next": {
      "type": "string",
      "description": "`ask` com `seconds`: grupo para onde o fluxo vai se o contato não responder no tempo. Sem ele, o fluxo acaba (`expired`)."
    },
    "request": {
      "$ref": "#/components/schemas/FlowRequest"
    },
    "error_next": {
      "type": "string",
      "description": "Grupo para onde o bloco leva quando falha: `request` (a chamada) e `set_contact` (número que não é WhatsApp)."
    },
    "conditions": {
      "type": "array",
      "maxItems": 20,
      "items": {
        "$ref": "#/components/schemas/FlowCondition"
      }
    },
    "match": {
      "type": "string",
      "enum": [
        "all",
        "any"
      ],
      "description": "`all` (padrão): todas as regras; `any`: alguma."
    },
    "next": {
      "type": "string",
      "description": "Grupo para onde a condição verdadeira leva."
    },
    "value": {
      "type": "string",
      "description": "Valor inicial do `set`, com `{{variável}}`. Vazio é o valor atual de `save_to`.",
      "example": "{{preco}}"
    },
    "operations": {
      "type": "array",
      "maxItems": 20,
      "items": {
        "$ref": "#/components/schemas/FlowOperation"
      }
    },
    "variables": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,39}$"
      },
      "description": "Variáveis que o `clear` apaga; vazio apaga todas."
    },
    "typing_seconds": {
      "type": "integer",
      "minimum": 0,
      "maximum": 60,
      "description": "Segundos mostrando \"digitando…\" antes de enviar."
    },
    "limit": {
      "$ref": "#/components/schemas/FlowLimit"
    },
    "limit_next": {
      "type": "string",
      "description": "Grupo para onde o fluxo vai quando o bloco passa do limite; sem ele, o fluxo para."
    },
    "seconds": {
      "type": "integer",
      "minimum": 0,
      "maximum": 2592000,
      "description": "`wait` e `schedule_flow`: de 1 a 2592000 (30 dias). `ask`: tempo limite da resposta, até 2592000; 0 (ou ausente) espera até 24 h. `chatbot` com `action: pause`: quanto tempo pausar; 0 pausa até alguém religar."
    },
    "flow_id": {
      "type": "string",
      "description": "Fluxo do `schedule_flow` e do `go_to_flow` (precisa estar publicado na hora)."
    },
    "fields": {
      "type": "array",
      "maxItems": 26,
      "items": {
        "$ref": "#/components/schemas/FlowLeadField"
      },
      "description": "`lead`: campos do lead a gravar."
    },
    "column": {
      "type": "string",
      "description": "`lead`: coluna do kanban para onde o lead vai (uma etiqueta com kanban ligado). A coluna anterior sai; as outras etiquetas ficam."
    },
    "action": {
      "type": "string",
      "enum": [
        "pause",
        "resume"
      ],
      "description": "`chatbot`: `pause` pausa a IA, os gatilhos e os fluxos da conversa (por `seconds`) e encerra o fluxo; `resume` religa e segue."
    },
    "agent_id": {
      "type": "string",
      "description": "`ai` e `ai_message`: agente (`GET /chatbot/agents`)."
    },
    "to": {
      "type": "string",
      "description": "Telefone com DDI (`5511999999999`) ou `{{variável}}`; grupo, canal e LID não valem. No `set_contact`, o contato que o fluxo passa a usar; no `send`, manda a mensagem para outro número (ex.: avisar a equipe) e o fluxo segue com o contato da conversa.",
      "example": "{{telefone}}"
    },
    "note": {
      "type": "string",
      "maxLength": 2000,
      "description": "Anotação de quem montou o fluxo; a execução não lê."
    }
  }
}
```

## #/components/schemas/FlowMessage

```json
{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "text",
        "image",
        "video",
        "audio",
        "voice",
        "document",
        "location",
        "contacts",
        "payment"
      ],
      "description": "Tipo da mensagem. Os campos dele vão soltos, ao lado de `type`, no formato do envio."
    }
  },
  "required": [
    "type"
  ],
  "description": "Mensagem do bloco `send`, no formato do [envio](/docs/enviar) (`type` e os campos do tipo), sem `to`: o destino é o contato do fluxo (ou o `to` do bloco). Tipos aceitos: `text`, `image`, `video`, `audio`, `voice`, `document`, `location`, `contacts` e `payment`. `payment` só com a chave PIX (`pix`, sem `amount`), que sai como o botão de copiar a chave; `contacts` leva um contato. Arquivo só por `url` (base64 não é guardado no fluxo). Botões, lista e enquete ficam no bloco `ask`. Guarda só o texto (ou a legenda), o arquivo e o nome do arquivo: `view_once`, `animated`, `mime_type`, `thumbnail` e `link_preview` diferente do padrão respondem 400. Não aceita `mentions`, `mention_all` nem `forwarded`. Mensagem fora disso volta como `message_invalid` na lista de `flow_invalid`, ao publicar.",
  "oneOf": [
    {
      "properties": {
        "type": {
          "const": "text"
        }
      },
      "allOf": [
        {
          "$ref": "#/components/schemas/SendContentText"
        }
      ]
    },
    {
      "properties": {
        "type": {
          "const": "image"
        }
      },
      "allOf": [
        {
          "$ref": "#/components/schemas/SendContentImage"
        }
      ]
    },
    {
      "properties": {
        "type": {
          "const": "video"
        }
      },
      "allOf": [
        {
          "$ref": "#/components/schemas/SendContentVideo"
        }
      ]
    },
    {
      "properties": {
        "type": {
          "const": "audio"
        }
      },
      "allOf": [
        {
          "$ref": "#/components/schemas/SendContentAudio"
        }
      ]
    },
    {
      "properties": {
        "type": {
          "const": "voice"
        }
      },
      "allOf": [
        {
          "$ref": "#/components/schemas/SendContentVoice"
        }
      ]
    },
    {
      "properties": {
        "type": {
          "const": "document"
        }
      },
      "allOf": [
        {
          "$ref": "#/components/schemas/SendContentDocument"
        }
      ]
    },
    {
      "properties": {
        "type": {
          "const": "location"
        }
      },
      "allOf": [
        {
          "$ref": "#/components/schemas/SendContentLocation"
        }
      ]
    },
    {
      "properties": {
        "type": {
          "const": "contacts"
        }
      },
      "allOf": [
        {
          "$ref": "#/components/schemas/SendContentContacts"
        }
      ]
    },
    {
      "properties": {
        "type": {
          "const": "payment"
        }
      },
      "allOf": [
        {
          "$ref": "#/components/schemas/SendContentPayment"
        }
      ]
    }
  ],
  "unevaluatedProperties": false
}
```

## #/components/schemas/SendContentText

```json
{
  "type": "object",
  "properties": {
    "text": {
      "type": "string",
      "description": "O texto da mensagem (com a formatação do WhatsApp). Vai como está: um `>1` respondendo a uma enquete é texto (o voto é `type: poll_vote`; a resposta a botões, `interactive_reply`)."
    },
    "link_preview": {
      "oneOf": [
        {
          "type": "boolean",
          "description": "`true`: prévia automática do primeiro link (o mesmo que omitir). `false`: sem prévia."
        },
        {
          "type": "object",
          "properties": {
            "title": {
              "type": "string",
              "description": "Título da prévia."
            },
            "description": {
              "type": "string",
              "description": "Texto de descrição da prévia."
            },
            "image_url": {
              "type": "string",
              "description": "Link da imagem da prévia."
            },
            "large": {
              "type": "boolean",
              "description": "Mostra a imagem grande, em vez da miniatura."
            }
          },
          "additionalProperties": false
        }
      ],
      "description": "Prévia do link. Sem o campo, a api monta a prévia do primeiro link do texto; `false` desliga; um objeto define os dados da prévia."
    }
  },
  "required": [
    "text"
  ],
  "title": "texto"
}
```

## #/components/schemas/SendContentImage

```json
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "description": "Link público do arquivo. Use `url` ou `base64`."
    },
    "base64": {
      "type": "string",
      "description": "Arquivo em base64 (com ou sem o prefixo `data:`). Use `url` ou `base64`."
    },
    "text": {
      "type": "string",
      "description": "A legenda."
    },
    "mime_type": {
      "type": "string",
      "description": "Tipo do arquivo. Sem ele, a api descobre pelo conteúdo."
    },
    "filename": {
      "type": "string",
      "description": "Nome do arquivo (documentos)."
    },
    "thumbnail": {
      "type": "string",
      "description": "Miniatura JPEG em base64 (vídeo e documento). Sem ela, a api gera quando consegue."
    },
    "view_once": {
      "type": "boolean",
      "description": "Visualização única (foto, vídeo e voz). Só no WhatsApp Web: nos outros canais, `unsupported_for_channel`."
    }
  },
  "title": "imagem"
}
```

## #/components/schemas/SendContentVideo

```json
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "description": "Link público do arquivo. Use `url` ou `base64`."
    },
    "base64": {
      "type": "string",
      "description": "Arquivo em base64 (com ou sem o prefixo `data:`). Use `url` ou `base64`."
    },
    "text": {
      "type": "string",
      "description": "A legenda."
    },
    "mime_type": {
      "type": "string",
      "description": "Tipo do arquivo. Sem ele, a api descobre pelo conteúdo."
    },
    "filename": {
      "type": "string",
      "description": "Nome do arquivo (documentos)."
    },
    "thumbnail": {
      "type": "string",
      "description": "Miniatura JPEG em base64 (vídeo e documento). Sem ela, a api gera quando consegue."
    },
    "view_once": {
      "type": "boolean",
      "description": "Visualização única (foto, vídeo e voz). Só no WhatsApp Web: nos outros canais, `unsupported_for_channel`."
    },
    "animated": {
      "type": "boolean",
      "description": "Envia como GIF: o vídeo toca em loop e sem som, como os GIFs do WhatsApp (mande um MP4). Só no WhatsApp Web: nos outros canais, `unsupported_for_channel`. Só vale em `type: video`."
    }
  },
  "title": "vídeo"
}
```

## #/components/schemas/SendContentAudio

```json
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "description": "Link público do arquivo. Use `url` ou `base64`."
    },
    "base64": {
      "type": "string",
      "description": "Arquivo em base64 (com ou sem o prefixo `data:`). Use `url` ou `base64`."
    },
    "text": {
      "type": "string",
      "description": "A legenda."
    },
    "mime_type": {
      "type": "string",
      "description": "Tipo do arquivo. Sem ele, a api descobre pelo conteúdo."
    },
    "filename": {
      "type": "string",
      "description": "Nome do arquivo (documentos)."
    },
    "thumbnail": {
      "type": "string",
      "description": "Miniatura JPEG em base64 (vídeo e documento). Sem ela, a api gera quando consegue."
    },
    "view_once": {
      "type": "boolean",
      "description": "Visualização única (foto, vídeo e voz). Só no WhatsApp Web: nos outros canais, `unsupported_for_channel`."
    }
  },
  "title": "áudio"
}
```

## #/components/schemas/SendContentVoice

```json
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "description": "Link público do arquivo. Use `url` ou `base64`."
    },
    "base64": {
      "type": "string",
      "description": "Arquivo em base64 (com ou sem o prefixo `data:`). Use `url` ou `base64`."
    },
    "text": {
      "type": "string",
      "description": "A legenda."
    },
    "mime_type": {
      "type": "string",
      "description": "Tipo do arquivo. Sem ele, a api descobre pelo conteúdo."
    },
    "filename": {
      "type": "string",
      "description": "Nome do arquivo (documentos)."
    },
    "thumbnail": {
      "type": "string",
      "description": "Miniatura JPEG em base64 (vídeo e documento). Sem ela, a api gera quando consegue."
    },
    "view_once": {
      "type": "boolean",
      "description": "Visualização única (foto, vídeo e voz). Só no WhatsApp Web: nos outros canais, `unsupported_for_channel`."
    }
  },
  "title": "voz"
}
```

## #/components/schemas/SendContentDocument

```json
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "description": "Link público do arquivo. Use `url` ou `base64`."
    },
    "base64": {
      "type": "string",
      "description": "Arquivo em base64 (com ou sem o prefixo `data:`). Use `url` ou `base64`."
    },
    "text": {
      "type": "string",
      "description": "A legenda."
    },
    "mime_type": {
      "type": "string",
      "description": "Tipo do arquivo. Sem ele, a api descobre pelo conteúdo."
    },
    "filename": {
      "type": "string",
      "description": "Nome do arquivo (documentos)."
    },
    "thumbnail": {
      "type": "string",
      "description": "Miniatura JPEG em base64 (vídeo e documento). Sem ela, a api gera quando consegue."
    },
    "view_once": {
      "type": "boolean",
      "description": "Visualização única (foto, vídeo e voz). Só no WhatsApp Web: nos outros canais, `unsupported_for_channel`."
    }
  },
  "title": "documento"
}
```

## #/components/schemas/SendContentLocation

```json
{
  "type": "object",
  "properties": {
    "latitude": {
      "type": "number",
      "description": "Latitude."
    },
    "longitude": {
      "type": "number",
      "description": "Longitude."
    },
    "name": {
      "type": "string",
      "description": "O nome do lugar."
    },
    "address": {
      "type": "string",
      "description": "Endereço."
    }
  },
  "required": [
    "latitude",
    "longitude"
  ],
  "title": "localização"
}
```

## #/components/schemas/SendContentContacts

```json
{
  "type": "object",
  "required": [
    "contacts"
  ],
  "properties": {
    "contacts": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nome exibido."
          },
          "phones": {
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "type": "string",
                  "description": "Número com DDI."
                },
                {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "number"
                  ],
                  "properties": {
                    "number": {
                      "type": "string",
                      "description": "Número com DDI."
                    },
                    "wa_id": {
                      "type": "string",
                      "description": "Ignorado no envio (vem na mensagem recebida)."
                    }
                  }
                }
              ]
            },
            "description": "Telefones: o número com DDI, ou o objeto da mensagem recebida (`{\"number\": \"...\"}`), para reenviar como veio."
          },
          "email": {
            "type": "string",
            "description": "E-mail."
          },
          "organization": {
            "type": "string",
            "description": "Empresa."
          },
          "url": {
            "type": "string",
            "description": "Site."
          }
        },
        "required": [
          "name",
          "phones"
        ],
        "additionalProperties": false
      },
      "description": "Contato a compartilhar. Por enquanto vai um por mensagem: a lista precisa ter exatamente um item (com mais, responde 400).",
      "minItems": 1,
      "maxItems": 1
    }
  },
  "title": "contatos"
}
```

## #/components/schemas/SendContentPayment

```json
{
  "type": "object",
  "properties": {
    "amount": {
      "type": "number",
      "description": "Valor. Sem `amount` e só com `pix`, sai o botão de copiar a chave; nesse caso `title`, `note`, `footer`, `currency` e `reference_id` respondem 400."
    },
    "currency": {
      "type": "string",
      "description": "Moeda (padrão `BRL`)."
    },
    "reference_id": {
      "type": "string",
      "description": "Número do pedido ou da fatura."
    },
    "order_id": {
      "type": "string",
      "description": "Mensagem de pedido (`order`) recebida do contato que esta cobrança atende: o `id` dela."
    },
    "title": {
      "type": "string",
      "description": "Título."
    },
    "text": {
      "type": "string",
      "description": "A nota da cobrança."
    },
    "footer": {
      "type": "string",
      "description": "Rodapé."
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Item."
          },
          "quantity": {
            "type": "number",
            "description": "Quantidade, inteiro a partir de 1."
          },
          "amount": {
            "type": "number",
            "description": "Valor do item. Ainda não é aceito (400): use `payment.amount`."
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false
      },
      "description": "Itens da cobrança. Por enquanto, um item por cobrança.",
      "maxItems": 1
    },
    "pix": {
      "type": "object",
      "properties": {
        "key": {
          "type": "string",
          "description": "Chave PIX."
        },
        "key_type": {
          "type": "string",
          "enum": [
            "cpf",
            "cnpj",
            "phone",
            "email",
            "evp"
          ],
          "description": "Tipo da chave (padrão `evp`)."
        },
        "merchant_name": {
          "type": "string",
          "description": "Recebedor."
        }
      },
      "required": [
        "key"
      ],
      "additionalProperties": false
    },
    "boleto": {
      "type": "string",
      "description": "Linha digitável do boleto."
    },
    "link": {
      "type": "string",
      "description": "Link de pagamento (só domínios homologados pelo WhatsApp)."
    },
    "invoice": {
      "type": "object",
      "properties": {
        "url": {
          "type": "string",
          "description": "Link público do arquivo. Use `url` ou `base64`."
        },
        "base64": {
          "type": "string",
          "description": "Arquivo em base64 (com ou sem o prefixo `data:`). Use `url` ou `base64`."
        },
        "mime_type": {
          "type": "string",
          "description": "Tipo do arquivo. Sem ele, a api descobre pelo conteúdo."
        },
        "filename": {
          "type": "string",
          "description": "Nome do arquivo (documentos)."
        },
        "thumbnail": {
          "type": "string",
          "description": "Miniatura JPEG em base64 (vídeo e documento). Sem ela, a api gera quando consegue."
        },
        "view_once": {
          "type": "boolean",
          "description": "Visualização única (foto, vídeo e voz). Só no WhatsApp Web: nos outros canais, `unsupported_for_channel`."
        }
      },
      "additionalProperties": false,
      "description": "Fatura (PDF): o arquivo, sem legenda."
    }
  },
  "title": "cobrança"
}
```

## #/components/schemas/FlowOption

```json
{
  "type": "object",
  "description": "Opção de uma pergunta. A escolha leva a `next`; sem `next`, segue o bloco seguinte do grupo.",
  "required": [
    "id",
    "label"
  ],
  "additionalProperties": false,
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{1,64}$",
      "description": "ID da opção, único no bloco."
    },
    "label": {
      "type": "string",
      "description": "Texto da opção. Também casa com a resposta digitada (sem diferença de maiúsculas) ou com o número da opção."
    },
    "next": {
      "type": "string",
      "description": "Grupo para onde a escolha leva."
    }
  }
}
```

## #/components/schemas/FlowRequest

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "method",
    "url"
  ],
  "description": "Chamada do bloco `request`. Em `url`, `headers` e `body`, `{{variável}}` é trocado pela variável do fluxo, por `{{phone}}` (número do contato) ou pelos placeholders do lead (`{{name}}`, `{{lead_*}}`…); desconhecido fica vazio. Na URL o valor é escapado; no corpo, entra como texto JSON. O host da URL é fixo (sem variável, para os cabeçalhos não irem para outro servidor), e variável com um trecho `.` ou `..` no caminho (como `../admin`) faz a chamada falhar. Tempo limite de 15 s; resposta de até 1 MB.",
  "properties": {
    "method": {
      "type": "string",
      "enum": [
        "GET",
        "POST",
        "PUT",
        "PATCH",
        "DELETE"
      ]
    },
    "url": {
      "type": "string",
      "description": "http:// ou https://, com `{{variável}}` no caminho ou na query.",
      "example": "https://api.exemplo.com/clientes?telefone={{phone}}"
    },
    "headers": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/FlowRequestHeader"
      },
      "description": "Cabeçalhos da chamada (ex.: a chave da API de destino); `{{variável}}` vale no valor."
    },
    "body": {
      "type": "string",
      "description": "Corpo em JSON (ignorado no GET), enviado com `Content-Type: application/json`. `{{variável}}` vale como valor (`{\"id\": {{id}}}`) ou dentro de texto (`\"{{name}}\"`).",
      "example": "{\"nome\": \"{{name}}\", \"telefone\": \"{{phone}}\"}"
    },
    "save": {
      "type": "array",
      "maxItems": 20,
      "items": {
        "$ref": "#/components/schemas/FlowRequestSave"
      },
      "description": "Campos da resposta guardados em variáveis quando ela vem com 2xx."
    }
  }
}
```

## #/components/schemas/FlowRequestHeader

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "name",
    "value"
  ],
  "properties": {
    "name": {
      "type": "string",
      "example": "Authorization"
    },
    "value": {
      "type": "string",
      "example": "Bearer {{token_api}}"
    }
  }
}
```

## #/components/schemas/FlowRequestSave

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "path",
    "to"
  ],
  "properties": {
    "path": {
      "type": "string",
      "description": "Caminho no JSON da resposta, com ponto entre as chaves e o índice da lista como número (`cliente.nome`, `pedidos.0.id`). Vazio guarda a resposta inteira. Texto vai sem aspas; objeto e lista, em JSON; o que não existe, vazio.",
      "example": "cliente.nome"
    },
    "to": {
      "type": "string",
      "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,39}$",
      "description": "Variável do fluxo que recebe o valor (até 4 KB; o resto é cortado).",
      "example": "nome_cliente"
    }
  }
}
```

## #/components/schemas/FlowCondition

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "value",
    "operator"
  ],
  "description": "Regra da condição. `value` e `compare` aceitam `{{variável}}`, `{{phone}}` e os campos do lead. Texto compara sem diferença de maiúsculas e sem espaço nas pontas; `equal` entre dois números compara o número (`10` e `10,0`); `greater`/`less` leem número brasileiro ou americano (`R$ 1.234,56`, `1234.56`) e são falsos se um lado não for número; regex é RE2, como foi escrita (`(?i)` ignora maiúsculas).",
  "properties": {
    "value": {
      "type": "string",
      "example": "{{assunto}}",
      "description": "O que conferir, em geral uma `{{variável}}`."
    },
    "operator": {
      "type": "string",
      "enum": [
        "equal",
        "not_equal",
        "contains",
        "not_contains",
        "greater",
        "less",
        "empty",
        "not_empty",
        "match_regex",
        "not_match_regex"
      ]
    },
    "compare": {
      "type": "string",
      "description": "Valor com que `value` é comparado (texto, número ou regex). Obrigatório, menos em `empty` e `not_empty`.",
      "example": "preço"
    }
  }
}
```

## #/components/schemas/FlowOperation

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "op"
  ],
  "description": "Operação do `set`, sobre o valor que vem da anterior. `arg` e `with` aceitam `{{variável}}`.\n\n| op | arg / with | Resultado |\n|---|---|---|\n| `add`, `subtract`, `multiply`, `divide` | número | Conta; valor que não é número (ou divisão por zero) vira vazio |\n| `to_brl` | — | `R$ 1.234,50` |\n| `br_number` | casas decimais (padrão 2) | `1.234,50` |\n| `replace` | texto a trocar / por qual | Troca todas as ocorrências |\n| `replace_regex` | regex / por qual (`$1` vale) | Troca o que casar |\n| `extract_regex` | regex | O primeiro grupo, ou o trecho todo; sem casar, vazio |\n| `upper_case`, `lower_case`, `capitalize`, `trim` | — | Maiúsculas, minúsculas, Iniciais Maiúsculas, sem espaço nas pontas |\n| `first_chars`, `last_chars` | quantos | Os primeiros ou os últimos caracteres |\n| `json` | caminho (`cliente.telefone`, `itens.0.id`) | O campo do JSON (o payload do POST em `{{webhook}}`, uma resposta guardada); texto sai sem aspas, o que não existe sai vazio |",
  "properties": {
    "op": {
      "type": "string",
      "enum": [
        "add",
        "subtract",
        "multiply",
        "divide",
        "to_brl",
        "br_number",
        "replace",
        "replace_regex",
        "extract_regex",
        "upper_case",
        "lower_case",
        "capitalize",
        "trim",
        "first_chars",
        "last_chars",
        "json"
      ]
    },
    "arg": {
      "type": "string",
      "description": "Argumento da operação (veja a tabela); `{{variável}}` vale."
    },
    "with": {
      "type": "string",
      "description": "Texto que entra no lugar, em `replace` e `replace_regex`."
    }
  }
}
```

## #/components/schemas/FlowLimit

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "times",
    "minutes"
  ],
  "description": "Anti-loop do bloco: no máximo `times` execuções para o mesmo contato em `minutes` minutos. `times: 0` desliga. Sem `limit`, vale 5 em 10 minutos.",
  "properties": {
    "times": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100
    },
    "minutes": {
      "type": "integer",
      "minimum": 1,
      "maximum": 1440
    }
  }
}
```

## #/components/schemas/FlowLeadField

```json
{
  "type": "object",
  "required": [
    "field",
    "value"
  ],
  "properties": {
    "field": {
      "type": "string",
      "description": "`lead_name`, `lead_fullName`, `lead_email`, `lead_personalid`, `lead_status`, `lead_notes` ou `lead_field01` a `lead_field20`.",
      "example": "lead_status",
      "enum": [
        "lead_name",
        "lead_fullName",
        "lead_email",
        "lead_personalid",
        "lead_status",
        "lead_notes",
        "lead_field01",
        "lead_field02",
        "lead_field03",
        "lead_field04",
        "lead_field05",
        "lead_field06",
        "lead_field07",
        "lead_field08",
        "lead_field09",
        "lead_field10",
        "lead_field11",
        "lead_field12",
        "lead_field13",
        "lead_field14",
        "lead_field15",
        "lead_field16",
        "lead_field17",
        "lead_field18",
        "lead_field19",
        "lead_field20"
      ]
    },
    "value": {
      "type": "string",
      "maxLength": 4096,
      "description": "Valor; `{{variável}}` vale. Vazio limpa o campo.",
      "example": "{{status}}"
    }
  }
}
```

## #/components/schemas/FlowSendWindow

```json
{
  "type": "object",
  "required": [
    "start",
    "end"
  ],
  "description": "Horário de envio: o que o fluxo manda sozinho (depois de `wait`, agendado ou começado por POST) só sai nesse horário; fora dele, a execução espera a abertura. Resposta a uma mensagem do contato sai na hora.",
  "properties": {
    "start": {
      "type": "string",
      "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
      "example": "08:00",
      "description": "Hora em que o envio abre (`HH:MM`)."
    },
    "end": {
      "type": "string",
      "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
      "description": "Hora em que o envio fecha (`HH:MM`), diferente de `start`. Antes de `start`, passa da meia-noite (22:00 às 06:00).",
      "example": "20:00"
    },
    "time_zone": {
      "type": "string",
      "description": "Fuso IANA; padrão `America/Sao_Paulo`.",
      "example": "America/Sao_Paulo"
    }
  }
}
```

## #/components/schemas/Flow

```json
{
  "type": "object",
  "description": "Fluxo do chatbot. `groups` é o rascunho; a execução segue a última versão publicada.",
  "required": [
    "id",
    "name",
    "start_group_id",
    "groups",
    "version",
    "has_unpublished_changes",
    "created_at",
    "updated_at",
    "webhook_key",
    "cancel_on_reply",
    "send_window"
  ],
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "description": "ID do fluxo, gerado pela api."
    },
    "name": {
      "type": "string",
      "description": "Nome do fluxo, para achar na lista; o contato não vê."
    },
    "start_group_id": {
      "type": "string",
      "description": "Grupo onde o fluxo começa."
    },
    "groups": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/FlowGroup"
      },
      "description": "Grupos do rascunho; a execução começa em `start_group_id`."
    },
    "cancel_on_reply": {
      "type": "boolean",
      "description": "Mensagem do contato cancela a execução que aguarda (bloco `wait` ou horário de envio) e os agendamentos deste fluxo para ele. A mensagem segue para os gatilhos."
    },
    "send_window": {
      "oneOf": [
        {
          "$ref": "#/components/schemas/FlowSendWindow"
        },
        {
          "type": "null"
        }
      ],
      "description": "Horário de envio do fluxo; `null` quando não tem."
    },
    "version": {
      "type": "integer",
      "readOnly": true,
      "description": "Última versão publicada; 0 se nunca foi publicado."
    },
    "published_at": {
      "readOnly": true,
      "description": "Última publicação. ISO 8601 em UTC com milissegundos. Ausente enquanto o fluxo nunca foi publicado (`version` 0).",
      "type": "string",
      "format": "date-time"
    },
    "has_unpublished_changes": {
      "type": "boolean",
      "readOnly": true,
      "description": "O rascunho difere da versão publicada (ou o fluxo nunca foi publicado)."
    },
    "webhook_key": {
      "type": "string",
      "description": "Chave do webhook público (48 caracteres hexadecimais); a URL é `POST /chatbot/flows/hook/{webhook_key}`. Vazia quando desligado. Quem tem a chave começa o fluxo sem token: trate como senha.",
      "pattern": "^([a-f0-9]{48})?$"
    },
    "created_at": {
      "description": "Criação. ISO 8601 em UTC com milissegundos.",
      "readOnly": true,
      "type": "string",
      "format": "date-time"
    },
    "updated_at": {
      "description": "Última alteração. ISO 8601 em UTC com milissegundos.",
      "readOnly": true,
      "type": "string",
      "format": "date-time"
    }
  }
}
```

## #/components/schemas/OrkesioError

```json
{
  "type": "object",
  "properties": {
    "error": {
      "type": "string",
      "description": "Código estável do erro, em inglês e `snake_case`. Os de uso geral: `invalid_request` (400): pedido inválido: JSON malformado, campo desconhecido ou valor fora do permitido (o detalhe em `message`; o campo em `field`, quando é um só); `unauthorized` (401): `token` ou `admintoken` ausente ou inválido; `forbidden` (403): o token não pode fazer isso (no servidor de demonstração, por exemplo); `not_admin` (403): o WhatsApp recusou por falta de permissão no grupo ou na newsletter; `account_restricted` (403): o WhatsApp restringiu a conta de iniciar conversas novas (mandar para quem já conversa continua valendo; até quando, em `GET /inbox/limits`); `new_chat_limit` (403): a conta atingiu a cota do WhatsApp de conversas novas neste ciclo (veja `GET /inbox/limits`); os dois também no `error` de `OrkesioMessage` e de `message.status`, e não adianta repetir o envio; `payment_required` (403): na API oficial, a Meta recusou o envio por um problema no pagamento da conta do WhatsApp Business (erro 131042 da Meta); acerte a forma de pagamento no gerenciador da Meta, porque repetir não adianta; também no `error` de `OrkesioMessage` e de `message.status`; `whatsapp_rejected` (400): o WhatsApp recusou a mudança pedida (o campo, em `field`, quando é um só); `not_found` (404): o recurso não existe nesta inbox; `conflict` (409): o recurso já existe ou mudou (id em uso, inbox de outro canal); `inbox_not_paired` (409): a inbox ainda não conectou um número; `payload_too_large` (413): o corpo passou do limite; `canceled` e `expired`: só no `error` de `OrkesioMessage` e de `message.status` (envio cancelado antes de sair, ou que passou do prazo na fila); `rate_limited` (429): muitos pedidos; espere o `Retry-After`; `internal_error` (500): falha inesperada na api; tente de novo; `send_failed` (500): o envio falhou (o motivo em `message`); também no `error` de `OrkesioMessage` e de `message.status`; `unsupported_for_channel` (501): o canal da inbox não atende esta rota ou este tipo de mensagem; `meta_error` (502): a Meta recusou ou não respondeu; `disconnected` (503): a inbox está desconectada (sem sessão do WhatsApp, ou token do canal oficial vencido); `unavailable` (503 ou 504): o WhatsApp não respondeu a tempo, ou algo em andamento impede agora; tente de novo. Cada rota lista os códigos próprios dela (`outside_window`, `number_invalid`, `flow_invalid`...). A lista pode crescer: trate código desconhecido pelo status HTTP."
    },
    "message": {
      "type": "string",
      "description": "Descrição para pessoas (às vezes o texto do WhatsApp ou da Meta). Não compare o texto: use `error`."
    },
    "field": {
      "type": "string",
      "description": "O campo do pedido que causou o erro, quando é um só (por exemplo, o campo que o WhatsApp recusou num `PATCH`)."
    }
  },
  "required": [
    "error",
    "message"
  ],
  "additionalProperties": true,
  "description": "O objeto de erro da Orkesio, o mesmo em todo lugar: o corpo das respostas de erro e o `error` de `OrkesioMessage`, `message.status` e `message.failed`. Algumas rotas juntam campos próprios (`request_id`, `errors`...)."
}
```

## #/components/responses/RateLimited

```json
{
  "description": "`rate_limited`: passou do limite de chamadas desta inbox (ou do admin); espere o tempo do header `Retry-After` e tente de novo.",
  "headers": {
    "Retry-After": {
      "description": "Segundos até poder chamar de novo.",
      "schema": {
        "type": "integer",
        "minimum": 1
      }
    }
  },
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/OrkesioError"
      },
      "example": {
        "error": "rate_limited",
        "message": "Muitas requisições."
      }
    }
  }
}
```

## Guias relacionados

[Visão geral](/docs/overview) · [O que funciona em cada canal](/docs/canais) · [O que muda da uazapi](/docs/o-que-muda)
