Appearance
API de Gerenciamento de Sessão
Endpoints HTTP que permitem a integrações externas (scripts de disparo, CRMs, automações) lerem e manipularem o estado de sessões ativas sem acesso direto ao Redis.
Visão geral
Quando um contato está em atendimento pelo flow, seu estado vive no Redis como um SessionState. Este guia cobre as três operações de integração externa:
| Método | Endpoint | O que faz |
|---|---|---|
GET | /sessions/:sessionId | Lê o SessionState completo |
PATCH | /sessions/:sessionId/variables | Mescla variáveis sem sobrescrever as demais |
POST | /sessions/:sessionId/redirect | Redireciona a sessão para um bloco/flow específico |
O mesmo módulo (
session.routes.ts) também expõe duas rotas de monitoramento/operação de sessões travadas:GET /sessions/frozen(lista sessões congeladas por erro de runtime) ePOST /sessions/:sessionId/unfreeze(action: "delete" | "resume"). Elas não fazem parte da API de integração externa e não são detalhadas aqui.
O sessionId segue o formato wa-{phoneNumberId}-{phoneNumber} para WhatsApp, wc-{channelId}-{userId} para WebChat e ig-{igUserId}-{fromId} para Instagram — é a mesma chave usada no Redis.
No WebChat com token de embed o id ganha um sufixo
-ctx{hash}(ex.:wc-abc-ext-42-ctx16700c7dc96f): cada contexto de abertura é uma conversa própria. Ver Canal WebChat.
Autenticação
Todos os endpoints exigem autenticação via JWT Bearer:
Authorization: Bearer <access_token>O token é obtido em POST /auth/login com credenciais de usuário do flow-ai-core.
Além do JWT, cada rota exige permissão RBAC no recurso sessions:
GETexigesessions:readPATCH /variablesePOST /redirectexigemsessions:write
Sem a permissão, a resposta é 403. O escopo por router se aplica apenas à leitura (GET /sessions/:sessionId e GET /sessions/frozen): um usuário restrito só enxerga sessões dos seus routers — sessões fora do escopo respondem 404. O PATCH /variables e o POST /redirect não aplicam esse escopo: qualquer usuário com sessions:write pode alterar/redirecionar uma sessão de qualquer router.
Endpoints
GET /sessions/:sessionId
Retorna o SessionState completo da sessão, incluindo variáveis, modo atual, bloco em execução, histórico de redirecionamentos e campos de sistema.
Resposta 200:
json
{
"mode": "flow",
"routerId": "cljxxx",
"flowId": "cljyyy",
"contactId": "cljzzz",
"chatId": "cljwww",
"currentBlockId": "cljblock",
"variables": { "nome": "João", "plano": "fibra-200" },
"source": "whatsapp",
"contactIdentity": "wa-123456789-5511999990000",
"currentFlow": { "id": "cljyyy", "name": "Atendimento Principal" },
"currentBlock": { "id": "cljblock", "name": "Pergunta de plano" },
"sessionHistory": [
{ "flow": { "id": "cljyyy", "name": "Atendimento Principal" }, "block": { "id": "cljblock", "name": "Pergunta de plano" } }
],
"expiryTimeout": 1800000,
"startedAt": 1717000000000,
"lastInteractionAt": 1717003600000
}| Status | Condição |
|---|---|
200 | Sessão encontrada |
404 | Sessão não existe no Redis (expirou ou nunca existiu) ou pertence a router fora do escopo do usuário |
403 | Sem permissão sessions:read |
401 | Token ausente ou inválido |
PATCH /sessions/:sessionId/variables
Mescla variáveis na sessão. Existentes são preservadas; o body substitui apenas as chaves informadas.
A mescla é um spread simples ({ ...session.variables, ...body.variables }): passar null não remove a chave — ela permanece com valor null. Para "limpar" uma variável, defina-a como null e trate esse valor como vazio no flow.
Body:
json
{
"variables": {
"plano": "fibra-500",
"cpf": null
}
}Resposta 200:
json
{
"ok": true,
"sessionId": "wa-123456789-5511999990000",
"variables": { "nome": "João", "plano": "fibra-500", "cpf": null }
}| Status | Condição |
|---|---|
200 | Variáveis atualizadas |
404 | Sessão não encontrada |
403 | Sem permissão sessions:write |
401 | Token ausente ou inválido |
POST /sessions/:sessionId/redirect
Redireciona a sessão para um bloco/flow específico. Publica um FlowStreamExternalJumpEvent em stream:flow; o engine consome e executa a partir do bloco destino.
Body:
json
{
"flowId": "cljyyy",
"blockId": "cljblock",
"executeOnEntry": true
}| Campo | Tipo | Default | Descrição |
|---|---|---|---|
flowId | string | obrigatório | ID do flow destino |
blockId | string | obrigatório | ID do bloco destino |
executeOnEntry | boolean | true | Se true, o engine executa imediatamente; se false, apenas reposiciona e aguarda próxima mensagem |
Resposta 200:
json
{
"ok": true,
"sessionId": "wa-123456789-5511999990000",
"flowId": "cljyyy",
"blockId": "cljblock",
"executeOnEntry": true,
"streamEntryId": "1717003600000-0"
}Cenários de erro:
| Status | Condição | Mensagem |
|---|---|---|
404 | Sessão não encontrada | "Sessão não encontrada" |
404 | Flow não existe ou sem publicação ativa | "Flow não encontrado ou sem publicação ativa" |
409 | Sessão em atendimento humano | "Sessão em atendimento humano — devolva ao fluxo antes de redirecionar" |
409 | flowId pertence a router diferente | "Cross-router redirect não permitido" |
422 | blockId não existe no flow | "Bloco destino não encontrado na definição do flow" |
403 | Sem permissão sessions:write | — |
401 | Token ausente ou inválido | — |
Fluxo do redirect externo
Cliente HTTP
│
│ POST /sessions/:sessionId/redirect
▼
flow-ai-core (valida + publica)
│
│ stream:flow { kind: "externalJump", flowId, blockId, executeOnEntry }
▼
flow-ai-engine (consome)
│ Atualiza redirectHistory, session.flowId, session.currentBlockId
│ Se executeOnEntry=true → walkBlocks()
▼
stream:outgoing-meta / outgoing-chat / outgoing-ig
│ (canal escolhido pelo prefixo do sessionId: wa-→meta, wc-→chat, ig-→ig)
▼
Contato recebe mensagens do novo blocoO redirect é assíncrono: o endpoint responde com 200 assim que o evento é publicado em stream:flow. A execução acontece no engine de forma independente.
Restrições e contratos
- Cross-router bloqueado: o
flowIddestino deve pertencer ao mesmorouterIdda sessão. Isso evita que integrações cruzem fronteiras de tenant. - Modo humano: o redirect só é bloqueado quando
session.mode === "human"(ticket humano aberto) — nesse caso responde409, e o agente deve encerrar o atendimento antes. Sessões emmode: "flow"emode: "agent"não são bloqueadas pelo endpoint. - executeOnEntry=false: útil quando a integração quer reposicionar o contato mas aguardar uma nova mensagem antes de executar (ex: inserir contexto via PATCH /variables primeiro, depois redirecionar com execute).
- redirectHistory: cada redirect externo empurra
{ flowId, blockId }noSessionState.redirectHistory, mantendo o mesmo contrato doredirectToBotaction interno.
Arquivos relevantes
| Arquivo | Papel |
|---|---|
services/flow-ai-core/src/http/routes/session.routes.ts | Definição dos endpoints |
packages/flow-ai-types/src/stream-events.ts | Tipo FlowStreamExternalJumpEvent (kind: "externalJump") |
packages/flow-ai-types/src/session.ts | Tipo SessionState |
services/flow-ai-engine/src/handlers/flow.ts | Despacho do evento externalJump |
services/flow-ai-engine/src/runtime/execute.ts | runFlowFromExternalJump() |
services/flow-ai-core/src/helpdesk/sessionId.ts | parsePhoneNumberIdFromSessionId() |