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
[
{
"token": []
}
]
{
"token": {
"name": "token",
"type": "apiKey",
"in": "header"
}
}
Corpo da requisição
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FlowInput"
},
"example": {
"flow_id": "rw1b2c3d4e5f6a7",
"name": "Recepção"
}
}
}
}
Respostas
{
"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 | |
| 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 emsnake_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
{
"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
{
"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}quot;,
"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
{
"type": "object",
"description": "Posição do grupo no editor.",
"required": [
"x",
"y"
],
"properties": {
"x": {
"type": "number"
},
"y": {
"type": "number"
}
}
}
#/components/schemas/FlowBlock
{
"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}quot;,
"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}quot;,
"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}quot;
},
"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
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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}quot;,
"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
{
"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
{
"type": "object",
"additionalProperties": false,
"required": [
"name",
"value"
],
"properties": {
"name": {
"type": "string",
"example": "Authorization"
},
"value": {
"type": "string",
"example": "Bearer {{token_api}}"
}
}
}
#/components/schemas/FlowRequestSave
{
"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}quot;,
"description": "Variável do fluxo que recebe o valor (até 4 KB; o resto é cortado).",
"example": "nome_cliente"
}
}
}
#/components/schemas/FlowCondition
{
"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
{
"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
{
"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
{
"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
{
"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]quot;,
"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]quot;,
"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
{
"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})?quot;
},
"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
{
"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
{
"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 · O que funciona em cada canal · O que muda da uazapi