Skip to content

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étodoEndpointO que faz
GET/sessions/:sessionIdLê o SessionState completo
PATCH/sessions/:sessionId/variablesMescla variáveis sem sobrescrever as demais
POST/sessions/:sessionId/redirectRedireciona 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) e POST /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:

  • GET exige sessions:read
  • PATCH /variables e POST /redirect exigem sessions: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
}
StatusCondição
200Sessão encontrada
404Sessão não existe no Redis (expirou ou nunca existiu) ou pertence a router fora do escopo do usuário
403Sem permissão sessions:read
401Token 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 }
}
StatusCondição
200Variáveis atualizadas
404Sessão não encontrada
403Sem permissão sessions:write
401Token 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
}
CampoTipoDefaultDescrição
flowIdstringobrigatórioID do flow destino
blockIdstringobrigatórioID do bloco destino
executeOnEntrybooleantrueSe 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:

StatusCondiçãoMensagem
404Sessão não encontrada"Sessão não encontrada"
404Flow não existe ou sem publicação ativa"Flow não encontrado ou sem publicação ativa"
409Sessão em atendimento humano"Sessão em atendimento humano — devolva ao fluxo antes de redirecionar"
409flowId pertence a router diferente"Cross-router redirect não permitido"
422blockId não existe no flow"Bloco destino não encontrado na definição do flow"
403Sem permissão sessions:write
401Token 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 bloco

O 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 flowId destino deve pertencer ao mesmo routerId da 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 responde 409, e o agente deve encerrar o atendimento antes. Sessões em mode: "flow" e mode: "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 } no SessionState.redirectHistory, mantendo o mesmo contrato do redirectToBot action interno.

Arquivos relevantes

ArquivoPapel
services/flow-ai-core/src/http/routes/session.routes.tsDefinição dos endpoints
packages/flow-ai-types/src/stream-events.tsTipo FlowStreamExternalJumpEvent (kind: "externalJump")
packages/flow-ai-types/src/session.tsTipo SessionState
services/flow-ai-engine/src/handlers/flow.tsDespacho do evento externalJump
services/flow-ai-engine/src/runtime/execute.tsrunFlowFromExternalJump()
services/flow-ai-core/src/helpdesk/sessionId.tsparsePhoneNumberIdFromSessionId()

flow-ai — plataforma proprietária de atendimento via WhatsApp