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.
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):
| Área | Scopes |
|---|---|
| Instâncias | instances:read, instances:write |
| Mensagens | messages:send, messages:read |
| Webhooks de conta | webhooks:read, webhooks:write |
| Stevo IA | ai:read, ai:write |
| StevoVoice | voice:read, voice:manage |
| Disparos | dispatch:read, dispatch:manage |
| Billing | billing:read, billing:purchase |
| GHL / Agência / Links | ghl: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 destrutivasRecreate 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:truequando repetir a mesma chamada faz sentido (429,5xx, falha de upstream);falsenos demais4xx— corrija a requisição antes de repetir.X-Request-Id: toda resposta traz esse header (e o mesmo valor emerror.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-LimiteX-RateLimit-Remaining. Ao estourar,429 rate_limitedcomRetry-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
| Rota | Motor | O que faz |
|---|---|---|
GET /v1/instances/{id}/connection | ambos | connected, logged_in, nome e número |
GET /v1/instances/{id}/health | ambos | saúde unificada (não falha se o servidor cair) |
GET /v1/instances/{id}/qr | SM v2 | QR atual (qr em data-URI) e pairing_code |
POST /v1/instances/{id}/qr/refresh | SM v2 | força um novo QR |
POST /v1/instances/{id}/disconnect | SM v2 | desconecta; { "logout": true } + ?confirm=true descarta a sessão (irreversível) |
DELETE /v1/instances/{id}?confirm=true | ambos | apaga 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), comcaption/filenameopcionais. Só API Oficial:cloud_apicom o payload Cloud API cru (ex.: template). - Resposta
201comengine,sentemessage_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.
| Resposta | Significado | O que fazer |
|---|---|---|
409 idempotency_conflict | mesma chave, corpo diferente | use outra chave |
409 idempotency_in_progress | a mesma chave ainda está sendo processada | espere e repita |
504 upstream_timeout | o servidor de envio não respondeu — pode ter enviado | consulte o status antes de reenviar |
502 upstream_unavailable | não alcançou o servidor da instância — nada foi enviado | pode 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.
| Campo | Regra |
|---|---|
url | https:// e destino público; redirects não são seguidos |
events | lista de tipos, ou ["*"] para todos |
max_in_flight | entregas simultâneas para esse destino: 1 a 64, default 8. Suba para endpoints que absorvem volume (ex.: agência com centenas de instâncias) |
active | false 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
2xxem até 10 segundos (processe em fila, se for demorado). Sem2xx, a Stevo reentrega: até 8 tentativas em cerca de 21h. - A entrega é "pelo menos uma vez": uma reentrega chega com o mesmo
event_ideattemptmaior. Deduplique peloevent_id. - Responder
410 Gonedesativa 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));
}
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
| Rota | O que faz |
|---|---|
GET /v1/webhooks/deliveries | lista 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}/retry | reenvia 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
- Crie uma API Key com
instances:read,messages:send,messages:read,webhooks:readewebhooks:write. - Crie as instâncias com
external_ref= id do cliente no seu sistema. - Crie um webhook de conta e guarde o
secret. - No endpoint: valide a assinatura, deduplique por
event_id, responda200rápido e processe em fila — useinstance.external_refpara saber de qual cliente é o evento. - Periodicamente (ex.: a cada hora), rode a reconciliação com
updated_sincepara pegar qualquer mudança perdida, inclusive exclusões. - Envie com
Idempotency-Key; em504 upstream_timeout, consulte o status pela chave antes de reenviar.
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
- 📖 Referência da API de Gestão — todos os endpoints, schemas e playground
- 📦 SDK
stevo-sdk(Node.js) - 📄 Especificação OpenAPI:
https://openapi.stevo.chat/openapi.yaml