Integre tudo
programaticamente.
REST. JSON. Auth por chave. Paginação cursor. Rate limit por chave e por empresa. Sem SDK obrigatório, sem dance OAuth — copie o curl, cole no seu backend e funciona.
https://codewo.com.br/api/v1120/min por chave
600/min por empresa
Visão geral
Toda request precisa de duas coisas: URL base + path e header Authorization: Bearer cwo_live_xxx. Respostas são sempre JSON. Códigos HTTP seguem REST padrão (200/201/4xx/5xx).
curl https://codewo.com.br/api/v1/me \
-H "Authorization: Bearer cwo_live_xxx"{
"company": { "id": 1, "name": "Acme Inc.", "slug": "acme" },
"api_key": {
"id": 42, "name": "Backend produção",
"prefix": "cwo_live_3tapst", "last_four": "zEuS",
"scopes": ["conversations:read", "messages:write"],
"last_used_at": "2026-04-23T18:32:01+00:00",
"expires_at": null
},
"rate_limit_per_minute": 120,
"api_version": "v1"
}Autenticação
Chaves cwo_live_* no padrão Stripe/Linear. SHA-256 em repouso, mostrado UMA vez na criação.
Hash em repouso
O segredo é exibido UMA vez na criação. O backend só guarda sha256(token).
Prefixo identificável
GitHub Secret Scanning detecta cwo_live_ em commits e revoga automaticamente.
Scopes
Cada chave carrega scopes. Endpoint sem o scope correto retorna 403 insufficient_scope.
| Scope | Operação | Permite |
|---|---|---|
conversations:read | read | Listar/ver conversas |
conversations:write | write | Atualizar status, atribuição |
messages:read | read | Histórico de mensagens |
messages:write | write | Enviar mensagens |
contacts:read | read | Listar/ver contatos |
contacts:write | write | Criar/editar contatos |
contacts:delete | delete | Remover contatos |
channels:read | read | Listar canais conectados |
users:read | read | Listar agentes (necessário pra obter sender_id) |
templates:read | read | Templates de mensagem e HSM |
Paginação
Todos os endpoints de listagem usam paginação cursor — mais previsível que offset durante updates frequentes.
?limitQuantidade por página. Default 25, máx 100.?cursorID do último item retornado. Use o valor de next_cursor da resposta anterior.{
"data": [ /* items... */ ],
"next_cursor": "37",
"has_more": true,
"limit": 25
}
// Próxima página: GET ?cursor=37&limit=25
// Quando has_more=false, fim da lista.Rate limit
Dois buckets independentes. Qualquer um excedido = 429.
Por chave
120/min
Bucket isolado por api_key_id. Chave comprometida não derruba as outras.
Por empresa
600/min
Cap agregado company_id. Impede multiplicar cota criando N chaves.
Headers
X-RateLimit-LimitLimite total na janela atual.
X-RateLimit-RemainingQuantas requests ainda cabem.
X-RateLimit-ResetTimestamp Unix em que a janela reseta.
X-RateLimit-ScopeQual bucket está mais apertado: key | company.
Retry-AfterEm respostas 429: segundos pra esperar antes de tentar de novo.
X-Request-IdID único da request (ULID). Cite no suporte.
Erros
Trate pelo campo code, não pela mensagem. A mensagem pode mudar; o code é estável.
{
"error": {
"code": "insufficient_scope",
"message": "This API key does not have the required scope: contacts:write.",
"required_scope": "contacts:write"
}
}{
"error": {
"code": "validation_failed",
"message": "O campo nome é obrigatório.",
"details": {
"name": ["O campo nome é obrigatório."],
"email": ["O e-mail informado é inválido."]
}
}
}| HTTP | code | Significado |
|---|---|---|
401 | missing_api_key | Header Authorization ausente. |
401 | invalid_api_key | Token não existe ou está mal formado. |
401 | revoked_api_key | A chave foi revogada. |
401 | expired_api_key | A chave passou da data de expiração. |
403 | ip_not_allowed | O IP de origem não está na allowlist da chave. |
403 | insufficient_scope | A chave não tem o scope necessário. |
404 | not_found | Recurso não existe ou pertence a outra empresa. |
405 | method_not_allowed | Método HTTP errado pra esse endpoint. |
422 | channel_inactive | Canal alvo está inativo. |
422 | channel_disconnected | Canal externo desconectado (precisa reautenticar). |
422 | validation_failed | Payload inválido. details traz cada erro por campo. |
429 | rate_limited | Excedeu o limite. Header Retry-After traz o tempo de espera. |
500 | internal_error | Erro inesperado do nosso lado. Já fomos notificados. |
Identidade
/v1/meDevolve a empresa dona da chave + metadado da chave + rate limit configurado. Use pra sanity-check do token.
{
"company": { "id": 1, "name": "Acme Inc.", "slug": "acme" },
"api_key": {
"id": 42, "name": "Backend produção",
"prefix": "cwo_live_3tapst", "last_four": "zEuS",
"scopes": ["conversations:read", "messages:write"],
"last_used_at": "2026-04-23T18:32:01+00:00",
"expires_at": null
},
"rate_limit_per_minute": 120,
"api_version": "v1"
}Usuários (agentes)
Lista os agentes da empresa. Use isso pra obter o sender_id que é obrigatório em POST /v1/conversations/{id}/messages. Pra integrações automáticas, crie um user dedicado tipo "API Bot" no painel e use o id dele sempre.
/v1/usersLista agentes da empresa.
Query params
qBusca em first_name, last_name, email.is_activetrue | false/v1/users/{id}Agente individual.
Canais
Canais conectados (WhatsApp, Telegram, Email, WebChat...). Read-only — credentials NUNCA são expostas.
/v1/channelsLista todos os canais da empresa, com status de sincronização.
Query params
typewhatsapp_web | whatsapp | telegram | email | webchat | facebook | instagram | smsis_activetrue | falselimit, cursorpaginação{
"data": [
{
"id": 2,
"name": "WhatsApp Pessoal",
"type": "whatsapp_web",
"provider": "wuzapi",
"is_active": true,
"sync_status": "connected",
"last_sync_at": "2026-04-23T13:00:00+00:00",
"error_message": null,
"created_at": "2026-04-22T10:00:00+00:00"
}
],
"next_cursor": "2",
"has_more": true,
"limit": 25
}/v1/channels/{id}Detalhes de um canal específico.
Conversas
/v1/conversationsLista conversas (open, pending, closed, etc.) com contato eager-loaded.
Query params
statusopen | pending | in_progress | resolved | closed | spamprioritylow | medium | high | urgentassigned_to_idfiltrar por user_id atribuídocontact_idconversas de um contato específico{
"data": [
{
"id": 38,
"contact": { "id": 17, "name": "Maria", "phone": "5511999999999", "email": null },
"contact_id": 17,
"status": "open",
"priority": "medium",
"assigned_to": { "type": "user", "id": 1 },
"message_count": 12,
"unread_count": 3,
"first_message_at": "2026-04-22T09:00:00+00:00",
"last_message_at": "2026-04-23T13:30:00+00:00",
"ai": { "summary": null, "sentiment": "neutral", "category": "vendas",
"intent": "comprar", "urgency_score": 4 },
"created_at": "2026-04-22T09:00:00+00:00",
"updated_at": "2026-04-23T13:30:00+00:00"
}
],
"next_cursor": "37",
"has_more": true,
"limit": 25
}/v1/conversations/{id}Conversa individual com contato carregado.
Mensagens
/v1/conversations/{id}/messagesHistórico de mensagens da conversa. Notas internas são excluídas por padrão.
Query params
include_internal_notespassar pra incluir notas internas/v1/conversations/{id}/messagesEnvia mensagem na conversa. Reusa o pipeline interno — dispara via canal externo (WhatsApp, Telegram, etc.).
Body
{
"content": "Olá! Recebemos sua solicitação.",
"sender_id": 5,
"content_type": "text",
"channel_id": 2,
"is_internal_note": false
}contentObrigatório. Texto da mensagem (max 10000).sender_idObrigatório. ID do agente que envia. Use GET /v1/users pra listar IDs válidos. Não tem fallback — atribuição é responsabilidade do cliente.content_typetext (default) | htmlchannel_idOpcional — se omitido, usa o último canal da conversa.is_internal_noteDefault false. Se true, persiste mas não envia ao canal.{
"data": {
"id": 1024,
"conversation_id": 38,
"channel_id": 2,
"channel_type": "whatsapp_web",
"sender_type": "user",
"sender_id": 1,
"content": "Olá! Recebemos sua solicitação.",
"content_type": "text",
"status": "pending",
"is_internal_note": false,
"sent_at": "2026-04-23T13:45:12+00:00",
"created_at": "2026-04-23T13:45:12+00:00"
}
}Erros de canal
channel_inactive(422) — canal está desabilitadochannel_disconnected(422) — WhatsApp/Telegram desconectado, precisa reautenticar
Contatos
CRUD completo. Soft delete (DELETE faz soft delete; restore não está exposto na v1).
/v1/contactsLista contatos com filtros.
Query params
qBusca em name, email, phone, document.statusactive | inactive | blockedsourcegoogle_ads, meta_ads, direct, manual, api...typepf | pj/v1/contacts/{id}Contato individual com endereço e attribution.
/v1/contactsCria contato. Source default = 'api' se não informado.
{
"name": "João Silva",
"email": "joao@empresa.com.br",
"phone": "+5511999998888",
"type": "pf",
"document": "123.456.789-00",
"company_name": "Empresa SA",
"city": "São Paulo",
"state": "SP",
"source": "site-form",
"metadata": { "lead_score": 85 }
}{
"data": {
"id": 23,
"name": "João Silva",
"email": "joao@empresa.com.br",
"phone": "+5511999998888",
"type": "pf",
"company_name": "Empresa SA",
"status": "active",
"source": "site-form",
"attribution": { "source": null, "campaign": null, "touchpoints_count": 0 },
"address": { "line": null, "city": "São Paulo", "state": "SP",
"country": "Brasil", "postal_code": null },
"first_contact_at": null,
"created_at": "2026-04-23T13:50:00+00:00"
}
}/v1/contacts/{id}Atualiza parcialmente. Só os campos enviados são alterados.
{
"notes": "Cliente VIP — atender com prioridade",
"metadata": { "lead_score": 95 }
}/v1/contacts/{id}Soft delete. Histórico de conversas e logs persistem.
{ "data": null, "deleted": true }Templates
Dois tipos: messages (atalhos internos do agente, ex: /oi) e HSM (templates Meta-aprovados pra WhatsApp Cloud).
/v1/templates/messagesTemplates internos de mensagem usados pelos agentes (atalhos).
Query params
qBusca em name, shortcut, content.categoryCategoria do template.is_activetrue | false/v1/templates/messages/{id}Template de mensagem individual.
/v1/templates/hsmTemplates HSM Meta-approved (WhatsApp Cloud), com componentes (header/body/footer/buttons).
Query params
statusapproved | pending | rejected | disabledlanguagept_BR, en_US, etc.channel_idFiltrar por canal específico/v1/templates/hsm/{id}Template HSM individual com todos os componentes.
Pronto pra primeira chamada?
Crie sua chave em segundos. Sem cartão, sem aprovação manual.