Pular para o conteúdo principal

API de Gestão — guia de integração

A API de Gestão (https://openapi.stevo.chat) é a porta de entrada oficial para integrar sistemas com a Stevo: instâncias, envio de mensagens, webhooks, Stevo IA, disparos, StevoVoice, GHL e billing. Este guia mostra como usar — a lista completa de endpoints, com playground, está na Referência da API de Gestão.

Usa Node.js/TypeScript?

O SDK stevo-sdk faz tudo isto por você — retry seguro, validação de webhook e tipos. Este guia é para quem integra em outra linguagem (PHP, Python, Go, n8n, Make...) ou quer entender o que acontece por baixo.

1. Autenticação​

Crie uma API Key no painel (menu do perfil → API Keys) e mande em todo request:

curl https://openapi.stevo.chat/v1/me \
--header 'Authorization: Bearer stevo_sk_SUA_CHAVE'

GET /v1/me mostra a conta, os scopes e a restrição da key — é o melhor endpoint para testar a configuração.

Scopes​

Cada key só faz o que os scopes permitem (403 insufficient_scope caso contrário):

ÁreaScopes
Instânciasinstances:read, instances:write
Mensagensmessages:send, messages:read
Webhooks de contawebhooks:read, webhooks:write
Stevo IAai:read, ai:write
StevoVoicevoice:read, voice:manage
Disparosdispatch:read, dispatch:manage
Billingbilling:read, billing:purchase
GHL / Agência / Linksghl:manage, agency:read, agency:manage, links:generate

Keys antigas com instances:manage e ai:manage continuam funcionando (são sinônimos de :write).

instances:write inclui operações destrutivas

Recreate total, logout e exclusão de instância usam o mesmo scope. Não existe scope separado para elas — dê instances:write só a quem precisa.

Key restrita a instâncias​

Uma key pode ser limitada a algumas instâncias (allowed_instance_ids em /v1/me; null = todas). Fora da lista, a API responde 404 not_found, exatamente como para uma instância de outra conta. Operações de conta (criar instância, webhooks de conta) respondem 403 key_restricted / 403 instance_restricted_key. Use uma key restrita por cliente sempre que a integração atender um cliente só.

2. Erros, correlação e rate limit​

Todo erro tem o mesmo formato:

{ "error": { "code": "not_ready", "message": "instância não conectada", "request_id": "req_3f9c...", "retryable": false } }
  • retryable: true quando repetir a mesma chamada faz sentido (429, 5xx, falha de upstream); false nos demais 4xx — corrija a requisição antes de repetir.
  • X-Request-Id: toda resposta traz esse header (e o mesmo valor em error.request_id). Você pode mandar o seu próprio (até 128 caracteres [A-Za-z0-9._:-]) e ele é ecoado. Informe-o ao suporte ao reportar um problema.
  • Rate limit por key: toda resposta traz X-RateLimit-Limit e X-RateLimit-Remaining. Ao estourar, 429 rate_limited com Retry-After (segundos).

3. Instâncias​

3.1. Amarre a instância ao seu sistema: external_ref e metadata​

# Criar numa vaga livre, já com a referência do seu cliente
curl https://openapi.stevo.chat/v1/instances \
--header 'Authorization: Bearer stevo_sk_SUA_CHAVE' \
--header 'Content-Type: application/json' \
--data '{ "name": "loja-centro", "external_ref": "WSP-000123", "metadata": { "plano": "pro" } }'

# Encontrar pela sua referência
curl --get https://openapi.stevo.chat/v1/instances \
--header 'Authorization: Bearer stevo_sk_SUA_CHAVE' \
--data-urlencode 'external_ref=WSP-000123'

# Atualizar (metadata SUBSTITUI o objeto inteiro; external_ref: null limpa)
curl -X PATCH https://openapi.stevo.chat/v1/instances/UUID_DA_INSTANCIA \
--header 'Authorization: Bearer stevo_sk_SUA_CHAVE' \
--header 'Content-Type: application/json' \
--data '{ "metadata": { "plano": "enterprise" } }'

external_ref: até 128 caracteres, único por conta (409 external_ref_conflict). metadata: até 16 pares texto→texto. Os dois viajam em todo evento dos webhooks de conta.

3.2. Reconciliação — só o que mudou​

GET /v1/instances com updated_since vira uma consulta incremental, incluindo as instâncias apagadas:

curl --get https://openapi.stevo.chat/v1/instances \
--header 'Authorization: Bearer stevo_sk_SUA_CHAVE' \
--data-urlencode 'updated_since=2026-09-22T00:00:00Z' \
--data-urlencode 'limit=100'
{
"data": [ { "id": "...", "external_ref": "WSP-000123", "connected": true, "updated_at": "..." } ],
"deleted": [ { "id": "...", "external_ref": "WSP-000099", "metadata": null, "deleted_at": "..." } ],
"next_cursor": "eyJ1...",
"has_more": true,
"deleted_has_more": false
}

Siga next_cursor (mande de volta em cursor) enquanto has_more for true. Se deleted_has_more vier true, repita a chamada com updated_since = último deleted_at recebido. Sem updated_since, a resposta continua a mesma de sempre ({ data, count }).

3.3. Conexão, QR e exclusão​

RotaMotorO que faz
GET /v1/instances/{id}/connectionambosconnected, logged_in, nome e número
GET /v1/instances/{id}/healthambossaúde unificada (não falha se o servidor cair)
GET /v1/instances/{id}/qrSM v2QR atual (qr em data-URI) e pairing_code
POST /v1/instances/{id}/qr/refreshSM v2força um novo QR
POST /v1/instances/{id}/disconnectSM v2desconecta; { "logout": true } + ?confirm=true descarta a sessão (irreversível)
DELETE /v1/instances/{id}?confirm=trueambosapaga a instância e libera o slot (irreversível, idempotente)

Sem ?confirm=true nas operações irreversíveis, a API responde 400 confirmation_required e não executa nada. A instância apagada aparece em deleted na reconciliação e gera o evento instance.deleted.

4. Envio de mensagens​

POST /v1/instances/{id}/messages (scope messages:send) envia uma mensagem, em SM v2 ou API Oficial, sem você lidar com o servidor da instância:

curl https://openapi.stevo.chat/v1/instances/UUID_DA_INSTANCIA/messages \
--header 'Authorization: Bearer stevo_sk_SUA_CHAVE' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: pedido-9f21' \
--data '{ "to": "5511999999999", "text": "Olá!" }'
  • Texto: text. Mídia: media_url + media_type (image, video, audio, document), com caption/filename opcionais. Só API Oficial: cloud_api com o payload Cloud API cru (ex.: template).
  • Resposta 201 com engine, sent e message_id. Instância desconectada → 409 not_ready.
  • Para volume, prefira os disparos ou o servidor da instância direto.

4.1. Idempotency-Key — nunca duplicar​

Mande Idempotency-Key (1–200 caracteres, único por instância, válido por 24h). Repetir a mesma requisição não reenvia: a API devolve a resposta original com o header Idempotent-Replayed: true.

RespostaSignificadoO que fazer
409 idempotency_conflictmesma chave, corpo diferenteuse outra chave
409 idempotency_in_progressa mesma chave ainda está sendo processadaespere e repita
504 upstream_timeouto servidor de envio não respondeu — pode ter enviadoconsulte o status antes de reenviar
502 upstream_unavailablenão alcançou o servidor da instância — nada foi enviadopode repetir

4.2. Status da mensagem​

# Pelo id da mensagem
curl https://openapi.stevo.chat/v1/instances/UUID_DA_INSTANCIA/messages/ID_DA_MENSAGEM \
--header 'Authorization: Bearer stevo_sk_SUA_CHAVE'

# Pela chave de idempotência — a consulta certa depois de um timeout
curl --get https://openapi.stevo.chat/v1/instances/UUID_DA_INSTANCIA/messages \
--header 'Authorization: Bearer stevo_sk_SUA_CHAVE' \
--data-urlencode 'idempotency_key=pedido-9f21'

Estados: queued → sent → delivered → read, ou failed (scope messages:read). As transições nunca regridem e batem com os eventos message.* dos webhooks. Depois de um upstream_timeout, o status fica queued — e evolui sozinho para sent/delivered se a mensagem de fato saiu.

5. Webhooks de conta​

Um webhook de conta recebe os eventos de todas as instâncias num envelope único, normalizado entre SM v2 e API Oficial, assinado e com entrega confiável. É o recomendado para integrações novas (o webhook por instância, /v1/instances/{id}/webhook, continua existindo, sem assinatura).

5.1. Criar​

curl https://openapi.stevo.chat/v1/webhooks \
--header 'Authorization: Bearer stevo_sk_SUA_CHAVE' \
--header 'Content-Type: application/json' \
--data '{
"url": "https://meu-sistema.com/webhooks/stevo",
"events": ["message.received", "message.failed", "instance.connected", "instance.disconnected"],
"description": "Meu CRM",
"max_in_flight": 16
}'

A resposta traz o secret (whsec_...) — só nesta resposta; depois, só secret_last4. Guarde num cofre/variável de ambiente.

CampoRegra
urlhttps:// e destino público; redirects não são seguidos
eventslista de tipos, ou ["*"] para todos
max_in_flightentregas simultâneas para esse destino: 1 a 64, default 8. Suba para endpoints que absorvem volume (ex.: agência com centenas de instâncias)
activefalse pausa; a API também desativa sozinha após 410 Gone ou 5 entregas esgotadas seguidas (disabled_reason: gone / too_many_failures)

Gerencie com GET/PATCH/DELETE /v1/webhooks/{id}. Limite de 10 webhooks por conta. Exige key sem restrição de instâncias.

Eventos: instance.created, instance.updated, instance.connecting, instance.connected, instance.disconnected, instance.auth_failed, instance.qr.updated, instance.deleted, message.received, message.sent, message.delivered, message.read, message.failed, message.edited, message.deleted, message.reaction.

5.2. O que chega no seu endpoint​

POST /webhooks/stevo HTTP/1.1
Content-Type: application/json
X-Stevo-Event-Id: evt_01J...
X-Stevo-Timestamp: 1790190000
X-Stevo-Attempt: 1
X-Stevo-Signature: sha256=5b1c...

{
"event_id": "evt_01J...",
"type": "message.received",
"attempt": 1,
"created_at": "2026-09-22T14:03:11.000Z",
"account_id": "UUID_DA_CONTA",
"instance": { "id": "UUID_DA_INSTANCIA", "external_ref": "WSP-000123", "metadata": { "plano": "pro" }, "engine": "smv2" },
"data": { "message_id": "3EB0...", "chat": "5511999999999", "from": "5511999999999", "from_me": false, "is_group": false, "type": "text", "text": "Olá!" }
}
  • Responda 2xx em até 10 segundos (processe em fila, se for demorado). Sem 2xx, a Stevo reentrega: até 8 tentativas em cerca de 21h.
  • A entrega é "pelo menos uma vez": uma reentrega chega com o mesmo event_id e attempt maior. Deduplique pelo event_id.
  • Responder 410 Gone desativa o webhook.

5.3. Validar a assinatura​

X-Stevo-Signature = sha256= + HMAC-SHA256(secret, timestamp + "." + corpoCru) em hexadecimal, onde secret é o valor completo whsec_... e timestamp é o X-Stevo-Timestamp. Rejeite também timestamps com mais de 5 minutos de diferença (proteção contra replay).

import hmac, hashlib, time

def assinatura_valida(corpo_cru: bytes, assinatura: str, timestamp: str, segredo: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False
esperado = hmac.new(segredo.encode(), f"{timestamp}.".encode() + corpo_cru, hashlib.sha256).hexdigest()
return hmac.compare_digest(assinatura.removeprefix("sha256="), esperado)
function assinatura_valida(string $corpoCru, string $assinatura, string $timestamp, string $segredo): bool {
if (abs(time() - (int) $timestamp) > 300) return false;
$esperado = hash_hmac('sha256', $timestamp . '.' . $corpoCru, $segredo);
return hash_equals($esperado, preg_replace('/^sha256=/', '', $assinatura));
}
Use o corpo CRU

Calcule o HMAC sobre os bytes exatos recebidos, antes de qualquer parse de JSON. Reserializar o JSON muda o corpo e a assinatura não confere.

5.4. Rotação do segredo​

POST /v1/webhooks/{id}/rotate-secret gera um segredo novo (devolvido uma vez). Durante a janela overlap_seconds (0–86400, default 86400 = 24h), cada entrega leva também X-Stevo-Signature-Previous, assinada com o segredo anterior — aceite qualquer uma das duas enquanto troca a configuração. overlap_seconds: 0 é a rotação de emergência (o antigo para de valer na hora).

5.5. Histórico e reenvio de entregas​

RotaO que faz
GET /v1/webhooks/deliverieslista entregas; filtros webhook_id, instance_id, status (pending, delivered, failed, exhausted), event_type, since, cursor, limit
GET /v1/webhooks/deliveries/{id}detalhe com o log de cada tentativa
POST /v1/webhooks/deliveries/{id}/retryreenvia agora, com o mesmo event_id (até 5 reenvios manuais)

Seu endpoint ficou fora do ar? Liste as entregas exhausted desde o incidente e reenvie — a deduplicação por event_id garante que nada é processado duas vezes.

6. Receita: sincronizar um sistema externo com a Stevo​

  1. Crie uma API Key com instances:read, messages:send, messages:read, webhooks:read e webhooks:write.
  2. Crie as instâncias com external_ref = id do cliente no seu sistema.
  3. Crie um webhook de conta e guarde o secret.
  4. No endpoint: valide a assinatura, deduplique por event_id, responda 200 rápido e processe em fila — use instance.external_ref para saber de qual cliente é o evento.
  5. Periodicamente (ex.: a cada hora), rode a reconciliação com updated_since para pegar qualquer mudança perdida, inclusive exclusões.
  6. Envie com Idempotency-Key; em 504 upstream_timeout, consulte o status pela chave antes de reenviar.
Nunca acesse o banco da Stevo

Integrações externas usam somente a API (ou o SDK). Supabase/Postgres, service_role e tabelas internas não fazem parte do contrato e podem mudar sem aviso.

7. MCP — a mesma API para agentes de IA​

O mesmo backend expõe um servidor MCP em https://openapi.stevo.chat/mcp (Streamable HTTP), com as mesmas operações como tools e autenticado pela mesma API key. Plugue no n8n (nó MCP Client), Claude ou Cursor.

Referências​