Pular para o conteúdo principal

API pública

A API pública é o jeito de o seu próprio sistema conversar com o seu agente: o seu backend manda a mensagem do usuário final, recebe a resposta e segue a vida. Não há navegador, não há login do usuário final, não há widget — é uma integração servidor-a-servidor, autenticada por uma chave de API de longa duração.

Use a API pública quando você quiser:

  • colocar o agente dentro do seu aplicativo ou site, com a sua interface;
  • atender por um canal que você mesmo opera (um app próprio, um sistema interno, um bot que você já tem);
  • automatizar conversas a partir do seu backend.
Quando não usar

Para colocar o agente numa página web sem escrever backend, use o widget; para WhatsApp, Telegram ou voz, use os canais prontos. A API pública é para quem quer controlar a experiência ponta a ponta.

Endereço base

Todas as chamadas abaixo são relativas a:

https://api.lab019.ai/gateway/v1/public

Tudo é HTTPS, corpo em JSON (application/json), respostas em JSON.

Autenticação

Existem duas superfícies nesta API, com credenciais diferentes:

SuperfícieQuem chamaCredencial
Conversa (sessões, mensagens, eventos)O seu backendCabeçalho x-api-key com a chave pak_…
Gerenciamento de chavesVocê (ou o seu painel/CLI)Cabeçalho Authorization: Bearer <token da sua conta>

A chave de API

Uma chave tem o formato:

pak_{key_id}.{secret}
  • key_id — a parte antes do ponto. É um identificador público: aparece nas listagens e é o que você usa nas rotas de gerenciamento.
  • secret — a parte depois do ponto. É o segredo de verdade. Só é exibido uma vez, na resposta de criação (ou de rotação) da chave. A plataforma guarda apenas um hash — não há como recuperá-lo depois. Se perdeu, rotacione.

Cada chave carrega a configuração da integração:

  • agente (agent_id) — qual agente responde às conversas dessa chave;
  • transferência para humano (handoff_enabled) — se as conversas podem ser assumidas por um atendente;
  • webhook de callback (callback_url, opcional) — para onde a plataforma também envia os eventos, além do stream (veja Webhook de callback);
  • tempo de sessão (session_ttl_seconds, opcional) — a janela de inatividade descrita em Conversas e sessões.

Criando a primeira chave

No painel, abra Configurações → Canais → API pública, escolha o agente e clique em criar. A chave completa aparece uma única vez — copie e guarde no cofre de segredos do seu sistema.

Pelo próprio API:

curl -X POST https://api.lab019.ai/gateway/v1/public/keys \
-H "Authorization: Bearer $MEU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"agent_id": "atendimento", "handoff_enabled": true}'
{
"key_id": "8Kd3vQpX2nRt",
"org_id": "org_abc123",
"agent_id": "atendimento",
"handoff_enabled": true,
"callback_url": "",
"prefix": "pak_9fLm2Q",
"last4": "b7Zk",
"created_at": "2026-08-06T13:20:41.512Z",
"updated_at": "2026-08-06T13:20:41.512Z",
"rotation_pending": false,
"session_ttl_seconds": null,
"api_key": "pak_8Kd3vQpX2nRt.9fLm2Q...b7Zk"
}

O campo api_key não volta em nenhuma outra resposta. prefix e last4 são apenas uma dica visual para você reconhecer a chave numa listagem.

Usando a chave

Toda chamada da superfície de conversa apresenta a chave inteira:

curl https://api.lab019.ai/gateway/v1/public/sessions \
-H "x-api-key: pak_8Kd3vQpX2nRt.9fLm2Q...b7Zk" \
-H "Content-Type: application/json" \
-d '{"external_user_id": "cliente-4711"}'

Não há token de sessão separado, nem login, nem refresh: a chave é a credencial, reapresentada em cada chamada.

Rotação sem downtime

Ao rotacionar, a chave antiga continua funcionando por uma janela de carência (24 horas por padrão, ajustável na chamada). Isso dá tempo de o seu deploy trocar o segredo sem derrubar requisições em voo. Depois da carência, só a nova vale.

Para um corte imediato, rotacione com grace_seconds: 0 — ou revogue a chave, o que invalida na hora tanto o segredo atual quanto qualquer segredo em carência.

Conversas e sessões

Você não cria nem guarda um id de conversa. Quem identifica o interlocutor é o external_user_id — o identificador estável do usuário no seu sistema (um id de cliente, um telefone, um hash — o que fizer sentido; até 256 caracteres).

O mesmo external_user_id sempre cai na mesma conversa. Não há sessão para guardar entre chamadas.

Janela de sessão. Depois de um período de inatividade (24 horas por padrão, configurável por chave), o próximo contato daquele external_user_id começa uma conversa nova — o histórico anterior não volta. Se um stream estiver aberto quando isso acontece, ele avisa com um evento conversation_rolled e passa a seguir a conversa nova.

Convenções

  • Correlação. Você pode enviar X-Correlation-Id em qualquer chamada; a resposta sempre devolve esse cabeçalho (ou um id novo, se você não mandar). É o identificador para citar em um chamado de suporte.
  • Erros seguem o formato application/problem+json (RFC 7807) — veja Erros.
  • Limites de taxa. Por padrão, 120 requisições por minuto por chave e por IP de origem (o operador da plataforma pode ajustar). Ao estourar, a resposta é 429 e o correto é aguardar e repetir.
  • Idempotência. O envio de mensagem exige um x-idempotency-key — repetir a chamada com a mesma chave devolve o mesmo resultado sem gerar uma segunda resposta do agente.

Endpoints de conversa

POST /sessions — resolver a conversa

Resolve (e aquece) a conversa de um usuário. Opcional: você pode ir direto ao envio de mensagem. Útil para descobrir o conversation_id antes de abrir o stream, ou para saber se a transferência para humano está habilitada.

Requisição

POST /sessions
x-api-key: pak_…
Content-Type: application/json

{ "external_user_id": "cliente-4711" }

Resposta 201

{
"session_id": "cliente-4711",
"external_user_id": "cliente-4711",
"conversation_id": "conv_9c1f…",
"agent_id": "atendimento",
"handoff_enabled": true
}

session_id é o próprio external_user_id, mantido por compatibilidade de formato — não é uma credencial e não expira.

POST /conversations/{external_user_id}/messages — enviar uma mensagem

Envia uma mensagem do usuário. A chamada retorna imediatamente: a resposta do agente é gerada em segundo plano e chega pelo stream de eventos (ou pelo webhook, no caso de atendimento humano).

Requisição

POST /conversations/cliente-4711/messages
x-api-key: pak_…
x-idempotency-key: 7f1e4b6a-2c9d-4f0e-9a11-2b3c4d5e6f70
Content-Type: application/json

{ "text": "Qual o prazo de entrega para o CEP 01310-100?" }

O cabeçalho x-idempotency-key é obrigatório — gere um valor único por mensagem (um UUID resolve). Repetir a chamada com a mesma chave devolve o mesmo messageId, sem rodar um segundo turno.

Resposta 202

{ "messageId": "3f9a1c8e5b2d4a67" }

Esse messageId é o que amarra a mensagem aos eventos do stream.

GET /conversations/{external_user_id}/events — stream de eventos (SSE)

Assina o fluxo de saída da conversa: os pedaços da resposta do agente, o uso de ferramentas, o fim do turno e os eventos de atendimento humano.

Requisição

GET /conversations/cliente-4711/events
x-api-key: pak_…
Accept: text/event-stream
Last-Event-ID: 1754487641512-0 # opcional, para retomar de onde parou

Respostatext/event-stream, no formato padrão de SSE:

event: state
data: {"driver":"agent_driving","openTurn":false}

id: 1754487641512-0
event: message_delta
data: {"delta":"O prazo para o CEP ","conversationId":"conv_9c1f…","messageId":"3f9a…","ts":1754487641.512,"correlationId":"…"}

id: 1754487641512-1
event: message_delta
data: {"delta":"01310-100 é de 2 dias úteis.","conversationId":"conv_9c1f…","messageId":"3f9a…","ts":1754487641.640,"correlationId":"…"}

id: 1754487641701-0
event: done
data: {"messageId":"3f9a…","conversationId":"conv_9c1f…","ts":1754487641.701,"correlationId":"…"}

Enquanto não há nada a enviar, o stream manda um comentário de keep-alive (: ping) — ignore-o.

Retomada. Cada evento vem com um id. Guardando o último id recebido e reenviando-o em Last-Event-ID, você recupera o que perdeu na queda de conexão. São retidos cerca de mil eventos por conversa: se o seu id for antigo demais, você recebe um único evento replay_gap (houve buraco) e o stream segue do ponto atual.

Eventos

EventoQuando aconteceCampos próprios
statePrimeiro evento de uma conexão nova (sem Last-Event-ID)driver (agent_driving ou human_driving), openTurn
message_deltaUm pedaço do texto da respostadelta
tool_startO agente começou a usar uma ferramentaname
tool_endA ferramenta terminouname
metadataMarcação de fim de processamento interno— (sempre vazio)
doneO turno terminoumessageId, e aborted: true quando foi interrompido
errorO turno falhouerrorCode, message (texto já apresentável ao usuário)
handoff.requestedA conversa foi para a fila de atendimento humanoreason
handoff.assignedUm atendente assumiuattendant.displayName
handoff.attendant_messageO atendente humano respondeutext, externalUserId, attachments (opcional)
handoff.returnedO atendimento voltou para o agente
replay_gapO Last-Event-ID é antigo demaisconversationId
conversation_rolledA janela de sessão virou; conversa novaconversationId

Todo evento de conversa carrega também conversationId, ts (epoch em segundos) e correlationId; os eventos de um turno carregam messageId.

O que a API pública não entrega

O raciocínio interno do agente e os detalhes de custo/roteamento não são expostos aqui — metadata chega vazio de propósito. A ferramenta interna de transferência para humano também não aparece em tool_start/tool_end.

Webhook de callback

Se o seu backend não mantém o stream aberto o tempo todo, configure um callback_url na chave. A plataforma envia os eventos de atendimento humano para lá além de publicá-los no stream — o que chegar primeiro, chegou.

Requisitos do endereço: HTTPS, host público (endereços internos e de loopback são recusados), sem usuário/senha embutidos na URL. Redirecionamentos não são seguidos.

O que a plataforma enviaPOST, JSON:

{
"event": "handoff.attendant_message",
"conversationId": "conv_9c1f…",
"externalUserId": "cliente-4711",
"text": "Oi! Aqui é a Ana, vou te ajudar com esse pedido.",
"attachments": [],
"ts": 1754487999.412,
"correlationId": "b3a9…"
}

Responda com 2xx. A entrega é best-effort e não tem retentativa: o stream de eventos continua sendo a via confiável.

Gerenciamento de chaves

Estas rotas são a área administrativa da sua organização — autenticam com o token da sua conta (Authorization: Bearer …), nunca com a pak_…. Toda resposta é restrita à sua organização; a chave de outra organização responde 404, não 403.

MétodoRotaO que faz
POST/keysCria uma chave — única resposta com o segredo
GET/keysLista as suas chaves (mascaradas)
GET/keys/{key_id}Detalha uma chave (mascarada)
PATCH/keys/{key_id}Altera agente, handoff, webhook ou tempo de sessão
POST/keys/{key_id}/rotateGera um novo segredo, com carência
DELETE/keys/{key_id}Revoga a chave (irreversível)

POST /keys

{
"agent_id": "atendimento",
"handoff_enabled": true,
"callback_url": "https://seu-backend.exemplo.com/webhook",
"session_ttl_seconds": 3600
}

agent_id é obrigatório. session_ttl_seconds omitido usa o padrão da plataforma; 0 significa "a conversa nunca expira por inatividade".

Resposta 201 — o objeto da chave com api_key.

GET /keys

{
"keys": [
{
"key_id": "8Kd3vQpX2nRt",
"org_id": "org_abc123",
"agent_id": "atendimento",
"handoff_enabled": true,
"callback_url": "https://seu-backend.exemplo.com/webhook",
"prefix": "pak_9fLm2Q",
"last4": "b7Zk",
"created_at": "2026-08-06T13:20:41.512Z",
"updated_at": "2026-08-06T15:02:07.880Z",
"rotation_pending": true,
"session_ttl_seconds": 3600
}
]
}

rotation_pending: true indica que existe um segredo anterior ainda dentro da janela de carência.

PATCH /keys/{key_id}

Só os campos enviados mudam. "callback_url": "" remove o webhook.

{ "handoff_enabled": false, "callback_url": "" }

Resposta 200 com o objeto atualizado (mascarado).

POST /keys/{key_id}/rotate

{ "grace_seconds": 3600 }

Corpo opcional ({} usa a carência padrão de 24 h; 0 corta na hora).

Resposta 200 — o objeto da chave, api_key com o novo segredo e previous_secret_expires_at (epoch em segundos, ou null quando não há carência).

DELETE /keys/{key_id}

Resposta 204, sem corpo. É definitivo: não existe "desabilitar e reativar".

Erros

Toda falha responde application/problem+json:

{
"type": "about:blank",
"title": "Unauthorized",
"status": 401,
"code": "public_api_key_invalid",
"detail": "malformed api key",
"correlation_id": "9c2b…"
}

Programe contra o campo codetitle e detail são texto para humano.

codeStatusO que houve
public_api_key_invalid401Chave ausente, malformada, desconhecida, revogada ou com segredo errado
public_api_invalid_external_user_id400external_user_id vazio ou acima de 256 caracteres
public_api_idempotency_key_required400Faltou o cabeçalho x-idempotency-key no envio
public_api_invalid_request400Corpo inválido (ex.: agent_id vazio, callback_url recusado)
public_api_rate_limited429Limite de requisições por minuto estourado
public_api_key_not_found404Não existe essa chave na sua organização
public_api_key_conflict409Conflito ao criar a chave — repita a chamada
unauthenticated401Token da conta ausente ou expirado (rotas de gerenciamento)
anonymous_principal_forbidden403O token não é de um usuário autenticado da organização
public_api_not_configured503A API pública não está habilitada nesta instalação

Por segurança, o 401 de chave inválida não diz qual verificação falhou — chave malformada, key_id inexistente e segredo errado devolvem a mesma resposta.

Um fluxo completo

API=https://api.lab019.ai/gateway/v1/public
KEY="pak_8Kd3vQpX2nRt.9fLm2Q...b7Zk"
USER=cliente-4711

# 1) (opcional) resolve a conversa
curl -s "$API/sessions" \
-H "x-api-key: $KEY" -H 'Content-Type: application/json' \
-d "{\"external_user_id\": \"$USER\"}"

# 2) abre o stream ANTES de enviar, para não perder o começo da resposta
curl -N "$API/conversations/$USER/events" \
-H "x-api-key: $KEY" -H 'Accept: text/event-stream' &

# 3) envia a mensagem
curl -s -X POST "$API/conversations/$USER/messages" \
-H "x-api-key: $KEY" \
-H "x-idempotency-key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{"text": "Qual o prazo de entrega para o CEP 01310-100?"}'

A ordem importa: abra o stream antes de enviar a mensagem. Se abrir depois, você só recebe os eventos a partir daquele instante — o começo da resposta já terá passado (e a retomada por Last-Event-ID depende de você já ter um id).

Boas práticas

  • Um external_user_id estável por usuário final. É ele que mantém o histórico; um id novo a cada chamada gera uma conversa nova a cada vez.
  • Um x-idempotency-key novo por mensagem, reutilizado apenas nas retentativas da mesma mensagem.
  • Guarde o último id recebido no stream e reenvie em Last-Event-ID ao reconectar.
  • Trate conversation_rolled limpando o que você mostrava do histórico anterior — dali em diante é outra conversa.
  • Guarde a chave num cofre de segredos, nunca no código nem no front-end: quem tem a pak_… fala com o seu agente em nome do seu sistema.