# Como funciona

Esta página mostra a Orkesio de ponta a ponta: as peças, o caminho de uma mensagem enviada e de uma recebida, e o que
chega em cada momento. Os detalhes de cada parte estão nas páginas seguintes.

## As peças

- **Inbox:** um número conectado a um canal. Cada inbox tem um `token`, que vai no header de toda chamada. A
  inbox da Orkesio é a mesma da uazapi: o mesmo token serve para as rotas antigas e para as novas.
- **Canal:** por onde a inbox fala. Hoje, o **WhatsApp pelo Web** (`whatsapp_web`, o número conectado por QR
  code), a **API oficial do WhatsApp** (`whatsapp_cloud`, pela Meta), o **Instagram** (`instagram`, uma conta
  profissional), o **Messenger** (`messenger`, uma Página do Facebook) ou o **Telegram** (`telegram`, um bot e, com o
  Telegram Business, a sua conta ligada a ele). O canal da inbox aparece em todo evento, em `inbox.channel`.
- **Mensagem:** um objeto só, a [`OrkesioMessage`](/docs/mensagem). É o mesmo na resposta do envio, na busca e nos eventos.
- **Três portas:**

| Para | Use | Página |
|---|---|---|
| Enviar | `POST /messages` | [Enviar mensagens](/docs/enviar) |
| Ler o que já aconteceu | `GET /messages` | [Buscar mensagens](/docs/buscar) |
| Saber na hora | `GET /events` (tempo real) ou webhook (`/webhooks`) | [Eventos](/docs/eventos) |

## Uma mensagem enviada, passo a passo

Exemplo: você envia um texto para um contato pelo WhatsApp Web.

| Passo | O que acontece | O que você vê |
|---|---|---|
| 1. Pedido | `POST /messages` com `to`, `type: "text"` e `text`. A api confere o pedido antes de qualquer coisa. | Erro no pedido (campo faltando, `to` inválido): **`400` na hora**, nada é enviado. Tipo que o canal não tem: `501`. |
| 2. Fila | O pedido entra na fila da inbox, que envia uma mensagem por vez e na ordem de chegada. | A resposta volta **na hora**, com a mensagem e `status: "queued"`. O `id` já é o definitivo. |
| 3. Saída | Na vez dela, a api manda a mensagem ao WhatsApp. | Evento **`message.sent`**, com a mensagem completa (placeholders trocados, arquivo processado). |
| 4. Entrega | O aparelho do contato recebe. | Evento **`message.status`** com `delivered`. |
| 5. Leitura | O contato abre a conversa (ou ouve o áudio). | **`message.status`** com `read` (ou `played`). |
| Se falhar | O WhatsApp recusa, ou a fila passa do prazo sem conseguir enviar. | **`message.status`** com `failed` e o motivo em `error` (`error.error: "expired"` quando passou do prazo). Quem só quer as falhas assina **`message.failed`**, que traz a mensagem inteira. |

Em qualquer momento, `GET /messages?message_id=<id>` devolve a mensagem com o status mais recente.

**Quer esperar o envio sair na própria resposta?** Mande `options.async: false`: a resposta só volta depois do passo
3, com `status: "pending"` (já saiu, sem confirmação de entrega ainda) ou com o erro.

**Na API oficial** o padrão é enviar na hora: o passo 2 não existe, e a resposta volta depois do passo 3, com
`status: "pending"` e o `wamid` como `id` (ou o erro). Lá não há fila: `options.async: true` é ignorado.

**Em grupo**, a entrega e a leitura chegam por participante, em [`group.receipts`](/webhook/group.receipts) (um lote a
cada 60 segundos). O `message.status` do grupo traz só a falha.

## Uma mensagem recebida, passo a passo

| Passo | O que acontece | O que você vê |
|---|---|---|
| 1. Chegada | O contato escreve. | Evento **`message.received`**: `data.message` (com quem escreveu, em `author`, e a conversa, em `chat_id`), `data.chat` (a conversa) e, em conversa direta, `data.lead`. |
| 2. Depois | O contato reage, edita ou apaga. | **`message.reaction`** ou **`message.updated`** (com `change: "edited"` ou `"deleted"` e a mensagem já com a mudança). |
| Mídia | Foto, vídeo, áudio ou documento. | O evento já traz o tipo, o tamanho e a miniatura (o arquivo ainda não). Quando a api baixa o arquivo (na chegada, se a inbox baixa as mídias, ou quando você pede o download), chega **`message.updated`** com `change: "media_ready"` e o `url`. |

O que você mesmo responde pelo celular ou pelo WhatsApp Web chega como **`message.sent`**, com `sent_by: "phone"` ou
`"web"`. Assim dá para ver a conversa inteira, não só o que passou pela API.

## Identificadores

- **`id`**: o identificador da mensagem no próprio canal (o id do WhatsApp; na API oficial, o `wamid`). É único
  dentro da inbox: se dois números seus estão no mesmo grupo, a mesma mensagem chega nas duas inboxes com o mesmo
  `id`. Guarde pela inbox (`inbox.id` do evento) e pelo `id`. Busca e ações valem para o número atual da inbox.
- **`chat_id`** (na mensagem; `chat.id` na conversa): a conversa. Num contato, o número no formato do WhatsApp
  (`5511999999999@s.whatsapp.net`); num grupo, o id do grupo (`...@g.us`). O LID, quando existe, vem em `chat.lid`.
- **`chat.lead_id`**: o lead do contato da conversa (veja abaixo). Guarde como veio, sem montar a partir de outro campo.

## Conversa e lead

São duas coisas, com rotas próprias:

- **Conversa** (`/chats`): o que o WhatsApp mostra, como nome, foto, não lidas, arquivada, etiquetas do WhatsApp
  Business e última mensagem. O atendimento também fica aqui: o responsável (`assigned_to`), o atendimento aberto
  (`ticket_open`), a pausa da automação e a memória da IA ([`PATCH /chats`](/endpoint/patch/chats)), porque valem
  para aquela conversa.
- **Lead** (`/leads`): quem é o cliente no CRM, com etapa do funil, tags do CRM, campos personalizados e notas. Só
  conversas diretas têm lead; grupo, canal e status não.

Três dados existem nos dois lados, com donos diferentes. Nenhum copia o outro:

| Dado | Na conversa (WhatsApp) | No lead (CRM da Orkesio) |
|---|---|---|
| Nome | `chat.name`: o da agenda do celular ou o que o contato usa | `lead.name` e `lead.full_name`: o que você ou a IA gravaram |
| Notas | `notes` em [`GET /chats/{chat_id}`](/endpoint/get/chats~:chat_id): as notas do WhatsApp Business, as mesmas do celular | `lead.notes`: só na Orkesio |
| Etiquetas | `chat.labels`: as do WhatsApp Business | `lead.tags`: as do CRM (colunas do kanban) |

O lado do WhatsApp é de cada número: cada um tem as suas notas e etiquetas, como no celular. O lado do CRM é do lead.

Como montar a tela:

- **Lista de conversas com o CRM:** `GET /chats`. Cada conversa direta traz `lead_id` e o `lead`.
- **Funil ou kanban:** [`GET /leads`](/endpoint/get/leads), filtrando por etapa ou tag. Por responsável:
  [`GET /chats`](/endpoint/get/chats) com `assigned_to`.
- **Alterar o CRM:** [`PATCH /leads`](/endpoint/patch/leads), com o `chat.lead_id`.
- **No webhook e no `/events`:** o evento de mensagem de conversa direta já traz o `lead` (`data.lead`), ao lado da mensagem e da conversa. A mudança sem mensagem chega em
  `lead.updated`.

Guarde o `lead_id` como opaco, como veio, e atualize-o pelo `lead.updated`.

## Erros

Toda resposta de erro tem o mesmo formato:

```json
{ "error": "invalid_request", "message": "text é obrigatório." }
```

| `error` | Status HTTP | Quando |
|---|---|---|
| `invalid_request` | 400 | Pedido errado: campo faltando, valor inválido, campo desconhecido no corpo. |
| `unsupported_for_channel` | 501 | O canal da inbox não tem esse recurso: um tipo de mensagem (ex.: enquete na API oficial) ou uma rota inteira (as rotas da uazapi nos canais oficiais). |
| `outside_window` | 400 | Canais oficiais: fora da janela de 24 horas. No WhatsApp, mande um modelo; no Instagram e no Messenger, espere o contato ou use `options.human_agent`. |
| `human_agent_not_allowed` | 400 | Instagram e Messenger: `human_agent` sem a permissão Human Agent no app. |
| `recipient_unavailable` | 400 | O contato bloqueou a conta ou não pode receber agora. No Telegram, também quando ele nunca falou com o bot. |
| `unauthorized` | 401 | Token inválido. |
| `conversation_taken` | 409 | Instagram e Messenger: a conversa está com outro app ou com a caixa de entrada da Meta. |
| `inbox_uses_whatsapp_web` | 409 | Ligar uma API oficial numa inbox que já foi conectada pelo QR code. Crie uma inbox nova. |
| `rate_limited` | 429 | Muitas requisições, da Orkesio ou do canal. |
| `disconnected` | 503 | A inbox não está conectada (no WhatsApp Web) ou o token do canal oficial venceu: conecte de novo. |
| `send_failed` | 500 | O canal recusou o envio. `message` traz o motivo. |
| `telegram_error` | 502 | Telegram: o Telegram não respondeu ao ligar a inbox. Tente de novo. |
| `internal_error` | 500 | Falha da nossa parte. Tente de novo. |

O `error` é estável: trate pelo código, não pelo texto de `message`, que pode mudar.

## Regras que valem para tudo

- **Nomes em `snake_case`** e **datas em ISO 8601, em UTC, com milissegundos** (`2026-10-17T12:00:00.000Z`),
  terminando em `_at`.
- **Listas paginadas por cursor:** `limit` e `cursor` na URL; a resposta traz `next_cursor` enquanto houver mais.
  Veja [Paginação](#paginacao).
- **Valores que podem crescer:** listas como `type`, `status`, `sent_by` e `channel` ganham valores novos com novos
  canais e recursos. Trate valor desconhecido sem quebrar.
- **Campo ausente é campo vazio:** campos sem valor não vêm no JSON (em vez de `null` ou `""`).
- **Uma mudança no WhatsApp por pedido:** nas rotas que alteram algo no WhatsApp, mande um campo de cada vez.
  Veja [o motivo](#uma-mudanca-no-whatsapp-por-pedido).

## Paginação

As listas longas da Orkesio paginam do mesmo jeito, por cursor:

1. Peça a primeira página com `limit` (de 1 a 200; padrão 50): `GET /chats?limit=50`.
2. A resposta traz a lista e, se houver mais, o `next_cursor`:

   ```json
   { "chats": [ ... ], "next_cursor": "MTc5MjIzODQwMDAwMHwzRUIwQTE" }
   ```

3. Para a próxima página, repita o mesmo pedido (mesmos filtros) com `cursor=<next_cursor>`:
   `GET /chats?limit=50&cursor=MTc5MjIzODQwMDAwMHwzRUIwQTE`.
4. Quando a resposta vier **sem** `next_cursor`, a lista acabou.

O cursor marca onde a página parou, e não uma posição: se chegar item novo enquanto você pagina, nada se repete nem
fica de fora. Guarde o cursor como veio, sem montar nem alterar: o formato pode mudar.

- **Não há número de página.** Para percorrer tudo, siga o `next_cursor` até ele não vir. O total só vem quando você
  pede (`total=true` em `GET /chats`, `GET /leads` e `GET /contacts/list`).
- **Listas que vêm do WhatsApp** (catálogo e newsletters) seguem o mesmo `cursor` e `next_cursor`, mas o tamanho da
  página pode ser menor: quem decide é o WhatsApp.
- **Listas curtas vêm inteiras**, sem `limit` nem `next_cursor`: os webhooks (até 5) e as inboxes do servidor
  (`GET /inbox/all`).
- **Rotas no formato da uazapi** (marcadas na página da rota) seguem paginando como na uazapi.

## Uma mudança no WhatsApp por pedido

As rotas que alteram um grupo, uma newsletter ou o perfil comercial aceitam **um campo por pedido**. Para mudar o nome
e a descrição de um grupo, faça dois pedidos:

```
PATCH /groups   {"group_id": "120363028782550019@g.us", "name": "Clientes VIP"}
PATCH /groups   {"group_id": "120363028782550019@g.us", "description": "Atendimento prioritário"}
```

Mais de um campo responde `400` com `error: "one_change_per_request"`, e nada muda.

**Por quê:** cada campo é uma operação separada no WhatsApp. Com vários num pedido só, se o WhatsApp recusar o
segundo (sem permissão, foto inválida, instabilidade), o primeiro já mudou e o grupo fica pela metade: você recebe um
erro, mas parte do pedido valeu. Com um por pedido, a resposta diz exatamente o que aconteceu: ou mudou, ou não mudou
nada.

Vale para:

| Rota | Um destes por pedido |
|---|---|
| [`PATCH /groups`](/endpoint/patch/groups) | `name`, `description`, `picture`, `announce`, `locked`, `join_approval`, `member_add_mode`, `ephemeral_seconds` |
| [`PATCH /newsletters`](/endpoint/patch/newsletters) | `name`, `description`, `picture`, `reaction_mode`, `muted` |
| [`PATCH /business-profiles/me`](/endpoint/patch/business-profiles~me) | `description`, `address`, `email` |

O que só muda na Orkesio (o lead, a conversa, a inbox) aceita vários campos juntos: lá não há etapa que possa
falhar pela metade.
