API pública · v1

Ligue sua loja, seu ERP e seu site ao Codewo.

API REST com JSON e chave por empresa: 29 endpoints em 12 recursos para ler e gravar contatos, conversas, mensagens, produtos e pedidos. Permissões por chave, lista de IPs e limite por minuto. Copie o curl abaixo e faça a primeira chamada.

URL base
https://staging.codewo.com.br/api/v1
Limite

120/min por chave

600/min por empresa

Disponível

A partir do plano Professional

e no teste grátis de 7 dias

Visão geral

Toda chamada leva a URL base, o caminho do recurso e o cabeçalho Authorization: Bearer cwo_live_…. As respostas são sempre JSON, com os códigos HTTP de costume (200, 201, 4xx, 5xx). O jeito mais rápido de conferir a chave é GET /me.

curl https://staging.codewo.com.br/api/v1/me \
  -H "Authorization: Bearer cwo_live_xxx"
Resposta 200 OK
json
{
  "company": { "id": 1, "name": "Loja Exemplo", "slug": "loja-exemplo" },
  "api_key": {
    "id": 42,
    "name": "Loja virtual",
    "prefix": "cwo_live_3tapst",
    "last_four": "zEuS",
    "scopes": ["contacts:read", "contacts:write", "orders:write"],
    "last_used_at": "2026-09-28T13:32:01-03:00",
    "expires_at": null
  },
  "rate_limit_per_minute": 120,
  "api_version": "v1"
}

Antes de começar

A partir do plano Professional

A API está no Professional, no Business e no teste grátis de 7 dias. Com a assinatura da empresa suspensa, ou cancelada depois do período pago, toda rota com chave responde 402 subscription_inactive.

A chave nasce na tela API & Chaves

Em Configurações, Integrações, API & Chaves, quem pode editar integrações cria a chave, escolhe as permissões e, se quiser, os IPs e a validade.

A chave é da empresa, não de uma pessoa

Ela enxerga os dados da empresa inteira, sem os recortes de cargo nem de atendimento exclusivo por canal. O que limita é a permissão da chave. Onde é preciso um autor (contato, pedido, carteira), entra quem criou a chave.

Dinheiro em centavos

Produtos e pedidos saem em centavos inteiros (campos *_cents), para ninguém somar dinheiro com ponto flutuante. A única entrada em reais é price e cost de POST /v1/products. Planos e adicionais saem em reais.

Os 29 endpoints

Doze recursos, cada um com a permissão que a chave precisa ter. Clique no recurso para ir aos detalhes.

RecursoMétodoCaminhoPermissão
SaúdeGET/v1/healthsem chave
IdentidadeGET/v1/mequalquer chave
UsuáriosGET/v1/usersusers:read
UsuáriosGET/v1/users/{id}users:read
CanaisGET/v1/channelschannels:read
CanaisGET/v1/channels/{id}channels:read
ConversasGET/v1/conversationsconversations:read
ConversasGET/v1/conversations/{id}conversations:read
MensagensGET/v1/conversations/{id}/messagesmessages:read
MensagensPOST/v1/conversations/{id}/messagesmessages:write
ContatosGET/v1/contactscontacts:read
ContatosGET/v1/contacts/{id}contacts:read
ContatosPOST/v1/contactscontacts:write
ContatosPATCH/v1/contacts/{id}contacts:write
ContatosDELETE/v1/contacts/{id}contacts:delete
ProdutosGET/v1/productsproducts:read
ProdutosGET/v1/products/{id}products:read
ProdutosPOST/v1/productsproducts:write
PedidosGET/v1/ordersorders:read
PedidosGET/v1/orders/{id}orders:read
PedidosPOST/v1/ordersorders:write
TemplatesGET/v1/templates/messagestemplates:read
TemplatesGET/v1/templates/messages/{id}templates:read
TemplatesGET/v1/templates/hsmtemplates:read
TemplatesGET/v1/templates/hsm/{id}templates:read
PlanosGET/v1/plansplans:read
PlanosGET/v1/plans/{slug}plans:read
AdicionaisGET/v1/addonsaddons:read
AdicionaisGET/v1/addons/{slug}addons:read

Autenticação

Cada chave tem o formato cwo_live_ + 6 caracteres de identificação + 32 caracteres aleatórios, e vai no cabeçalho Authorization: Bearer.

Mostrada uma vez

A chave completa aparece só na criação. O Codewo guarda o hash SHA-256 dela, que é comparado em toda chamada; na tela fica só o começo e os 4 últimos caracteres.

IPs permitidos e validade

Opcionalmente, restrinja a chave a uma lista de IPs (endereço exato ou faixa CIDR em IPv4) e defina uma data de expiração. Lista vazia aceita qualquer origem.

Revogar vale na hora

A chave revogada é recusada a partir da chamada seguinte. Permissões, IPs e validade são definidos na criação: para mudar, crie uma chave nova e revogue a antiga.

Uso de cada chave

Na tela API & Chaves, cada chave mostra o uso dos últimos 30 dias e as últimas 100 requisições, com status e tempo de resposta.

As 16 permissões

Cada chave carrega só as permissões que você marcar. Chamar um endpoint sem a permissão dele devolve 403 insufficient_scope. Uma chave para a loja virtual, por exemplo, pode ter só products:write e orders:write.

PermissãoTipoO que libera
conversations:read
Conversas
leitura
Listar e ver conversas.
conversations:write
Conversas
escrita
Reservado. Nenhum endpoint usa hoje: a API não altera conversa.
messages:read
Mensagens
leitura
Ler o histórico de mensagens de uma conversa.
messages:write
Mensagens
escrita
Enviar mensagem de texto numa conversa.
contacts:read
Contatos
leitura
Listar e ver contatos, com o dono da carteira.
contacts:write
Contatos
escrita
Criar e editar contatos, inclusive a carteira.
contacts:delete
Contatos
exclusão
Excluir contatos.
channels:read
Canais
leitura
Listar os canais conectados.
users:read
Usuários
leitura
Listar a equipe (é daqui que sai o sender_id).
templates:read
Templates
leitura
Templates de mensagem e modelos aprovados do WhatsApp (HSM).
plans:read
Planos
leitura
Catálogo de planos visível para a conta da chave.
addons:read
Adicionais
leitura
Catálogo de adicionais.
products:read
Catálogo
leitura
Ler o catálogo de produtos da empresa.
products:write
Catálogo
escrita
Criar e atualizar produtos (pelo SKU).
orders:read
Vendas
leitura
Ler pedidos, com linhas, totais e entrega.
orders:write
Vendas
escrita
Registrar vendas.

Paginação

Há dois formatos. Quase toda listagem pagina por cursor, que continua previsível enquanto a lista muda. Produtos e pedidos paginam por página e devolvem o total.

Cursor: usuários, canais, conversas, mensagens, contatos, templates, planos e adicionais

limitItens por página. Padrão 25, máximo 100.
cursorO next_cursor da resposta anterior. A lista vem do registro criado por último para o mais antigo, pelo id (planos e adicionais, na ordem de exibição).
Resposta com cursor
json
{
  "data": [ /* itens */ ],
  "next_cursor": "37",
  "has_more": true,
  "limit": 25
}

// Próxima página: GET ...?cursor=37&limit=25
// has_more = false: acabou a lista.

Página: produtos e pedidos

per_pageItens por página. Padrão 25; acima de 100 vira 100.
pageNúmero da página, a partir de 1. meta traz a página atual, a última e o total.
Resposta com página
json
{
  "data": [ /* itens */ ],
  "meta": {
    "current_page": 1,
    "last_page": 4,
    "per_page": 25,
    "total": 87
  }
}

// Próxima página: GET /v1/orders?page=2&per_page=25

Limite por minuto

Dois baldes independentes, contados por minuto. Passar de qualquer um devolve 429 rate_limited.

Por chave

120/min

Cada chave conta no próprio balde, então uma rotina apressada numa chave não gasta o limite por chave das outras.

Por empresa

600/min

Soma de todas as chaves da empresa. Criar mais chaves não multiplica o limite.

Cabeçalhos da resposta

X-RateLimit-Limit

Limite do balde mais apertado na janela de um minuto.

X-RateLimit-Remaining

Quantas chamadas ainda cabem nesse balde.

X-RateLimit-Reset

Momento (Unix) em que a janela reinicia.

X-RateLimit-Scope

Qual balde está mais apertado: key (a chave) ou company (a empresa).

Retry-After

Só no 429: quantos segundos esperar antes de tentar de novo.

X-Request-Id

Identificador único da chamada. Guarde nos seus logs para correlacionar. Não vem nas recusas da própria chave (401, 402, 403 de IP) nem no 429.

Erros

Todo erro vem no mesmo envelope. Trate pelo campo code, não pela mensagem: a mensagem pode mudar, o code é estável.

Formato padrão (403)
json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key does not have the required scope: orders:write.",
    "required_scope": "orders:write"
  }
}
Validação (422)
json
{
  "error": {
    "code": "validation_failed",
    "message": "O campo nome é obrigatório. (and 1 more error)",
    "details": {
      "name": ["O campo nome é obrigatório."],
      "email": ["O campo email deve ser um endereço de email válido."]
    }
  }
}
Assinatura inativa (402)
json
{
  "error": {
    "code": "subscription_inactive",
    "message": "Your subscription is not active. Reactivate to use the API.",
    "subscription_status": "suspended"
  }
}
HTTPcodeSignificado
401
missing_api_keyCabeçalho Authorization ausente.
401
invalid_api_keyA chave não existe ou está mal formada.
401
revoked_api_keyA chave foi revogada.
401
expired_api_keyA chave passou da data de expiração.
402
subscription_inactiveA assinatura da empresa dona da chave está suspensa, ou foi cancelada e o período pago acabou. Vale para toda rota com chave.
403
ip_not_allowedO IP de origem não está na lista de IPs permitidos da chave.
403
insufficient_scopeA chave não tem a permissão que o endpoint exige (required_scope diz qual).
404
not_foundO registro não existe ou é de outra empresa: nos dois casos, 404 not_found. Também volta quando o caminho não existe ou o método HTTP está errado para ele.
422
validation_failedDados inválidos. details traz os erros por campo.
422
channel_inactiveO canal da conversa está desativado.
422
channel_disconnectedCanal de WhatsApp, Telegram, Instagram ou Messenger desconectado. Reconecte antes de enviar.
429
rate_limitedPassou do limite por minuto. Retry-After diz quanto esperar.
500
internal_errorErro inesperado do nosso lado.
503
service_unavailableServiço indisponível no momento. Tente de novo em instantes.

Identidade e saúde

GET
/v1/me
qualquer chave

Devolve a empresa dona da chave, os dados da chave (permissões, último uso, validade) e o limite por minuto. Use para conferir se o token está certo.

Resposta 200 OK
json
{
  "company": { "id": 1, "name": "Loja Exemplo", "slug": "loja-exemplo" },
  "api_key": {
    "id": 42,
    "name": "Loja virtual",
    "prefix": "cwo_live_3tapst",
    "last_four": "zEuS",
    "scopes": ["contacts:read", "contacts:write", "orders:write"],
    "last_used_at": "2026-09-28T13:32:01-03:00",
    "expires_at": null
  },
  "rate_limit_per_minute": 120,
  "api_version": "v1"
}
GET
/v1/health
sem chave

Verificação pública, sem chave e sem limite por minuto. Confere as dependências críticas e informa a latência de cada uma. Serve para página de status e monitoramento.

Resposta 200 OK
json
{
  "status": "operational",
  "checks": {
    "database": { "ok": true, "latency_ms": 1 }
    /* ...um item por dependência crítica */
  },
  "timestamp": "2026-09-28T12:00:00-03:00"
}

Se alguma verificação falhar, a resposta é 503 com o mesmo formato e "status": "degraded". Trate pelo campo status, não pelos nomes dentro de checks.

Usuários

A equipe da empresa. É daqui que sai o sender_id obrigatório no envio de mensagem. Para integrações automáticas, vale criar um usuário dedicado (por exemplo, "Integração loja", que conta como um usuário do plano) e usar sempre o id dele.

GET
/v1/users
users:read

Lista a equipe da empresa.

Parâmetros de consulta

qBusca no nome, sobrenome e e-mail.
is_activetrue traz só quem está ativo; false, os inativos e os convidados. Sem o filtro, vêm todos.
limit, cursorPaginação por cursor.
Resposta 200 OK
json
{
  "data": [
    {
      "id": 5,
      "name": "Maria Clara Souza",
      "first_name": "Maria Clara",
      "last_name": "Souza",
      "email": "maria.clara@exemplo.com.br",
      "avatar_url": null,
      "is_active": true,
      "last_activity_at": "2026-09-28T10:12:00-03:00",
      "created_at": "2026-03-02T09:00:00-03:00"
    }
  ],
  "next_cursor": null,
  "has_more": false,
  "limit": 25
}

is_active vem da situação do usuário: true só para quem está ativo e não foi excluído.

GET
/v1/users/{id}
users:read

Um usuário da empresa.

Canais

Os canais conectados à Caixa de Entrada, com o estado da conexão. Só leitura: as credenciais do canal nunca saem pela API.

GET
/v1/channels
channels:read

Lista os canais da empresa, ativos e inativos.

Parâmetros de consulta

typewhatsapp (oficial) | whatsapp_web | instagram | facebook (Messenger) | telegram | email | webchat | sms
is_activetrue | false
limit, cursorPaginação por cursor.
Resposta 200 OK
json
{
  "data": [
    {
      "id": 2,
      "name": "Telegram da loja",
      "type": "telegram",
      "provider": null,
      "is_active": true,
      "sync_status": "connected",
      "last_sync_at": "2026-09-28T13:00:00-03:00",
      "error_message": null,
      "created_at": "2026-04-22T10:00:00-03:00"
    }
  ],
  "next_cursor": "2",
  "has_more": true,
  "limit": 25
}
GET
/v1/channels/{id}
channels:read

Um canal.

Conversas

As conversas da Caixa de Entrada, com o contato junto. Só leitura: a API não muda status nem atribuição.

GET
/v1/conversations
conversations:read

Lista as conversas da empresa.

Parâmetros de consulta

statusopen | pending | in_progress | resolved | spam | snoozed (adiada)
prioritylow | medium | high | urgent
assigned_to_idId de quem está com a conversa. O filtro não separa pessoa de equipe: confira assigned_to.type na resposta.
contact_idConversas de um contato.
limit, cursorPaginação por cursor.
Resposta 200 OK
json
{
  "data": [
    {
      "id": 38,
      "contact": { "id": 17, "name": "João Silva", "phone": "+5519999999999", "email": null },
      "contact_id": 17,
      "status": "open",
      "priority": "medium",
      "assigned_to": { "type": "user", "id": 5 },
      "message_count": 12,
      "agent_message_count": 7,
      "automated_message_count": 2,
      "contact_message_count": 3,
      "unread_count": 3,
      "first_message_at": "2026-09-27T09:00:00-03:00",
      "last_message_at": "2026-09-28T13:30:00-03:00",
      "resolved_at": null,
      "ai": { "summary": null, "sentiment": "neutral", "category": "comercial",
              "intent": "comprar", "urgency_score": 4 },
      "created_at": "2026-09-27T09:00:00-03:00",
      "updated_at": "2026-09-28T13:30:00-03:00"
    }
  ],
  "next_cursor": "37",
  "has_more": true,
  "limit": 25
}
  • assigned_to.type é user (pessoa), team (equipe) ou ai_agent (a atendente virtual).
  • unread_count é o total de mensagens menos as da equipe e as automáticas.
  • O bloco ai traz a análise da IA da conversa, quando houver.
GET
/v1/conversations/{id}
conversations:read

Uma conversa, com o contato.

Mensagens

Aninhadas na conversa. O envio usa o mesmo caminho de saída da Caixa de Entrada e vai pelo canal da conversa.

GET
/v1/conversations/{id}/messages
messages:read

Histórico de mensagens da conversa. As notas internas ficam de fora, a não ser que você peça.

Parâmetros de consulta

include_internal_notesPresente com qualquer valor (ex.: =1), inclui as notas internas. Sem ele, elas ficam de fora.
limit, cursorPaginação por cursor, da mensagem mais recente para a mais antiga.
  • sender_type diz quem enviou: contact (o cliente), agent (a equipe), bot, workflow (automação), broadcast (transmissão) ou system.
  • status: pending, sent, delivered, read, failed ou deleted.
  • Mensagem apagada pela equipe vem com content nulo.
  • channel_type e direction vêm na resposta, mas nulos: o canal está em channel_id, e quem enviou, em sender_type.
POST
/v1/conversations/{id}/messages
messages:write

Envia uma mensagem de texto na conversa. A resposta 201 quer dizer que a mensagem foi aceita e entrou na fila de envio; acompanhe o status pela leitura.

Exemplo
curl
curl -X POST https://staging.codewo.com.br/api/v1/conversations/38/messages \
  -H "Authorization: Bearer cwo_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"content": "Olá, João! Entregamos em Campinas, sim.", "sender_id": 5}'

Corpo

{
  "content": "Olá, João! Entregamos em Campinas, sim.",
  "sender_id": 5,
  "channel_id": 2,
  "is_internal_note": false
}
contentObrigatório. Texto da mensagem, até 10.000 caracteres.
sender_idObrigatório. Id de um usuário da empresa, que aparece como autor. Não há valor padrão: use GET /v1/users.
channel_id Canal de saída. Sem ele, vale o último canal usado na conversa.
is_internal_note true grava como nota interna e não envia ao cliente. Padrão false.
content_type text (padrão). A API envia só texto.
Resposta 201 Created
json
{
  "data": {
    "id": 1026,
    "conversation_id": 38,
    "channel_id": 2,
    "channel_type": null,
    "direction": null,
    "sender_type": "agent",
    "sender_id": 5,
    "content": "Olá, João! Entregamos em Campinas, sim.",
    "content_type": "text",
    "status": "pending",
    "is_internal_note": false,
    "sent_at": "2026-09-28T13:45:12-03:00",
    "delivered_at": null,
    "read_at": null,
    "created_at": "2026-09-28T13:45:12-03:00"
  }
}

Bom saber

  • channel_inactive (422): o canal da conversa está desativado.
  • channel_disconnected (422): o WhatsApp, Telegram, Instagram ou Messenger precisa ser reconectado.
  • No WhatsApp oficial, fora da janela de 24 horas desde a última mensagem do cliente, a Meta só aceita modelo aprovado, e a API envia só texto.

Contatos e carteira

Cadastro completo: listar, ver, criar, editar e excluir. Cada contato traz o dono da carteira (assigned_to com o id, e portfolio_owner com a pessoa), e a troca de dono pela API entra no mesmo histórico da tela, assinada por quem criou a chave.

GET
/v1/contacts
contacts:read

Lista os contatos com filtros. Use assigned_to=none para achar quem está sem dono.

Parâmetros de consulta

qBusca no nome, e-mail, telefone e documento.
statusactive | inactive | blocked
typepf | pj
sourceO campo source do cadastro (quem nasce pela API sem source recebe api).
assigned_toCarteira: id do dono, ou none para listar quem está sem dono.
limit, cursorPaginação por cursor.
GET
/v1/contacts/{id}
contacts:read

Um contato, com endereço, origem e dono da carteira.

POST
/v1/contacts
contacts:write

Cria um contato. Sempre cria: não procura duplicado por telefone nem e-mail.

Corpo
json
{
  "name": "João Silva",
  "email": "joao.silva@exemplo.com.br",
  "phone": "+5519999999999",
  "type": "pf",
  "document": "123.456.789-00",
  "city": "Campinas",
  "state": "SP",
  "source": "site-orcamento",
  "assigned_to": 5
}
name Obrigatório no POST. No PATCH, só se for mudar.
email, phone E-mail válido; telefone até 30 caracteres.
type, document, company_name pf | pj, CPF ou CNPJ, razão social.
address, city, state, country, postal_code Endereço. country padrão Brasil.
status active | inactive | blocked
source Origem que o seu sistema quer registrar. Padrão api.
assigned_to Dono da carteira: id de um usuário ativo da empresa. No PATCH, null tira o dono.
notes, metadata Observação e um objeto livre. São gravados, mas não voltam na leitura da API.
Resposta 201 Created
json
{
  "data": {
    "id": 17,
    "name": "João Silva",
    "email": "joao.silva@exemplo.com.br",
    "phone": "+5519999999999",
    "avatar_url": null,
    "type": "pf",
    "document": "123.456.789-00",
    "company_name": null,
    "status": "active",
    "source": "site-orcamento",
    "assigned_to": 5,
    "portfolio_owner": {
      "id": 5, "name": "Maria Clara Souza", "avatar_url": null, "is_active": true
    },
    "attribution": { "source": null, "campaign": null, "touchpoints_count": 0 },
    "address": { "line": null, "city": "Campinas", "state": "SP",
                 "country": "Brasil", "postal_code": null },
    "first_contact_at": null,
    "last_interaction_at": null,
    "created_at": "2026-09-28T13:50:00-03:00",
    "updated_at": "2026-09-28T13:50:00-03:00"
  }
}
PATCH
/v1/contacts/{id}
contacts:write

Atualiza só os campos enviados. Mandar assigned_to troca o dono da carteira; mandar null tira o dono.

Corpo (qualquer campo do cadastro)
json
{
  "city": "Valinhos",
  "assigned_to": 7
}

A troca de dono dispara o webhook contact.updated e pode disparar automações do gatilho de carteira, como qualquer troca feita na tela. O aviso diz que a carteira mudou, mas não traz o novo dono: para saber quem é, consulte o contato.

DELETE
/v1/contacts/{id}
contacts:delete

Exclui o contato. O histórico de conversas continua guardado.

Resposta 200 OK
json
{ "data": null, "deleted": true }

Produtos

O catálogo que a empresa vende. A loja virtual, o PDV ou o sistema próprio mantêm o catálogo atualizado daqui, e reenviar o mesmo produto atualiza em vez de duplicar. Não confunda com /v1/plans, que é o catálogo de planos do Codewo.

GET
/v1/products
products:read

Lista os produtos da empresa, com as variações, preços em centavos e estoque.

Parâmetros de consulta

searchBusca no nome, no SKU e no código de barras (GTIN).
skuProduto que tem uma variação com esse SKU exato.
statusdraft | active | inactive | archived
category_idProdutos de uma categoria.
page, per_pagePaginação por página (padrão 25, máximo 100).
GET
/v1/products/{id}
products:read

Um produto, com categoria e variações.

POST
/v1/products
products:write

Cria ou atualiza pelo SKU. Se algum produto da empresa já tem uma variação com esse SKU, ele é atualizado e a resposta é 200; se não, o produto é criado e a resposta é 201.

Corpo
json
{
  "sku": "TRV-ESP-50",
  "name": "Travesseiro de espuma 50 × 70 cm",
  "description": "Espuma D28 com capa de algodão.",
  "category_name": "Travesseiros",
  "unit": "UND",
  "price": 89.90,
  "cost": 41.50
}
nameObrigatório. Nome do produto, até 255 caracteres.
sku Código. É a chave do upsert: se algum produto da empresa já tem esse SKU, ele é atualizado.
price, cost Em reais (ex.: 89.90). É a única entrada em reais da API; a resposta devolve em centavos.
unit Código da lista de 49 unidades (un, pc, cx, m, m2, m3, l, kg, h...). A grafia de ERP, como UND, M2 ou PÇ, é traduzida.
category_name Nome da categoria. Se não existir, é criada.
status draft | active | inactive | archived. Na criação, padrão active.
description Descrição, até 5.000 caracteres.
Resposta 201 Created
json
{
  "data": {
    "id": 311,
    "name": "Travesseiro de espuma 50 × 70 cm",
    "description": "Espuma D28 com capa de algodão.",
    "status": "active",
    "unit": "un",
    "billing_period": null,
    "category": { "id": 12, "name": "Travesseiros" },
    "source": "integration",
    "created_at": "2026-09-28T14:02:10-03:00",
    "variants": [
      {
        "id": 540,
        "name": null,
        "sku": "TRV-ESP-50",
        "gtin": null,
        "price_cents": 8990,
        "cost_cents": 4150,
        "is_default": true,
        "is_active": true,
        "track_stock": false,
        "stock_quantity": 0.0
      }
    ]
  }
}

Repare: "unit": "UND" virou un, e "price": 89.90 voltou como "price_cents": 8990. Sem sku, cada envio cria um produto novo.

Pedidos

As vendas da empresa. Serve aos dois lados de uma integração: ler o que foi vendido no Codewo e registrar aqui o que foi vendido na loja. O pedido criado pela API é um pedido como os outros: nasce confirmado, baixa o estoque dos produtos que controlam estoque, dispara as automações de venda e, quando tem contato, aparece no painel ao lado da conversa do cliente.

GET
/v1/orders
orders:read

Lista os pedidos, com linhas, totais em centavos e situação da entrega.

Parâmetros de consulta

statusdraft | confirmed | cancelled
contact_idPedidos de um contato.
sinceData ISO 8601 (ex.: 2026-09-01). Pedidos com data da venda a partir dela.
page, per_pagePaginação por página (padrão 25, máximo 100).
GET
/v1/orders/{id}
orders:read

Um pedido, com contato, entrega, totais e linhas.

POST
/v1/orders
orders:write

Registra uma venda. Com external_id, reenviar é seguro: a segunda chamada devolve 200 com o pedido que já existe, sem venda em dobro e sem baixar o estoque de novo.

Exemplo
curl
curl -X POST https://staging.codewo.com.br/api/v1/orders \
  -H "Authorization: Bearer cwo_live_xxx" \
  -H "Content-Type: application/json" \
  -d @pedido.json
Corpo (pedido.json)
json
{
  "external_id": "LOJA-9001",
  "contact_id": 17,
  "sold_at": "2026-09-28T10:30:00-03:00",
  "expected_delivery_at": "2026-10-03",
  "shipping_cents": 2500,
  "notes": "Pedido da loja virtual.",
  "items": [
    { "product_id": 311, "quantity": 2, "unit_price_cents": 8990 },
    { "product_id": 312, "quantity": 1, "unit_price_cents": 12990,
      "discount_type": "percent", "discount_value": 10 }
  ]
}
itemsObrigatório. De 1 a 100 linhas. Cada uma aponta para um produto do catálogo da empresa.
items[].product_idObrigatório. Produto. De outra empresa, a resposta é 404.
items[].variant_id Variação. Sem ela, vale a variação padrão.
items[].quantity Quantidade. Padrão 1.
items[].unit_price_cents Preço praticado, em centavos. Sem ele, vale o preço de tabela.
items[].discount_type, discount_value percent (discount_value 10 = 10%) ou amount (discount_value em centavos).
items[].measurements.cuts Venda por medida: lista de cortes com pieces, length, width e height. A quantidade sai da conta dos cortes.
external_id Id do pedido no seu sistema. Reenviar o mesmo external_id devolve o pedido que já existe.
contact_id Contato da empresa. De outra empresa, 404.
assigned_to Responsável. Sem ele, quem criou a chave.
sold_at, expected_delivery_at Data da venda (padrão: agora) e previsão de entrega.
shipping_cents, notes Frete em centavos e observação.
Resposta 201 Created
json
{
  "data": {
    "id": 128,
    "number": "PED-2026-0042",
    "status": "confirmed",
    "source": "integration",
    "sold_at": "2026-09-28T10:30:00-03:00",
    "contact": { "id": 17, "name": "João Silva",
                 "email": "joao.silva@exemplo.com.br", "phone": "+5519999999999" },
    "delivery": {
      "status": "pending",
      "expected_at": "2026-10-03",
      "shipped_at": null,
      "delivered_at": null,
      "is_late": false
    },
    "recurrence": null,
    "totals": {
      "items_subtotal_cents": 30970,
      "items_discount_cents": 1299,
      "order_discount_cents": 0,
      "shipping_cents": 2500,
      "total_cents": 32171,
      "currency": "BRL"
    },
    "items": [
      {
        "id": 901, "product_id": 311, "variant_id": 540,
        "name": "Travesseiro de espuma 50 × 70 cm", "sku": "TRV-ESP-50",
        "quantity": 2.0, "unit_price_cents": 8990,
        "unit": "un", "sale_mode": null, "price_base_quantity": null, "measurements": null,
        "discount_cents": 0, "total_cents": 17980
      },
      {
        "id": 902, "product_id": 312, "variant_id": 541,
        "name": "Capa impermeável para colchão", "sku": "CAPA-IMP-138",
        "quantity": 1.0, "unit_price_cents": 12990,
        "unit": "un", "sale_mode": null, "price_base_quantity": null, "measurements": null,
        "discount_cents": 1299, "total_cents": 11691
      }
    ]
  }
}

Por que reenviar é seguro

O external_id vira a chave de origem do pedido, e o banco tem um índice único sobre ela. Retentativa por timeout, aviso e varredura chegando juntos: no fim existe um pedido só. Se duas chamadas chegarem exatamente ao mesmo tempo, as duas podem responder 201, mas com o mesmo pedido: compare pelo id, não pelo status. Sem external_id, cada chamada é uma venda nova, como no balcão.

Bom saber

O pedido criado pela API não recebe condição de pagamento nem parcelas, e a leitura não traz pagamento. Na venda por medida, unit_price_cents é o preço da base do produto, e a quantidade sai dos cortes.

Templates

Dois tipos, só leitura: os templates de mensagem que a equipe usa como atalho na conversa, e os modelos aprovados pela Meta (HSM) de cada canal de WhatsApp oficial, com cabeçalho, corpo, rodapé e botões.

GET
/v1/templates/messages
templates:read

Templates de mensagem da equipe.

Parâmetros de consulta

qBusca no nome, atalho e conteúdo.
categoryCategoria do template.
is_activetrue | false
limit, cursorPaginação por cursor.
GET
/v1/templates/messages/{id}
templates:read

Um template de mensagem.

GET
/v1/templates/hsm
templates:read

Modelos aprovados do WhatsApp oficial, com componentes e situação na Meta.

Parâmetros de consulta

statusAPPROVED | PENDING | REJECTED | DISABLED | PAUSED
languagept_BR, en_US...
channel_idModelos de um canal.
qBusca no nome.
limit, cursorPaginação por cursor.
GET
/v1/templates/hsm/{id}
templates:read

Um modelo aprovado, com todos os componentes.

Planos e adicionais

O catálogo do Codewo como a conta da chave o vê: planos com preço em reais, recursos e franquias, e os adicionais que se compram por cima do plano. Quem revende o Codewo com a própria marca usa estes endpoints para montar a página de preços do próprio site.

GET
/v1/plans
plans:read

Planos visíveis para a conta. O plano contratado vem com is_current = true.

Parâmetros de consulta

statusactive (padrão) ou all.
limit, cursorPaginação por cursor.
GET
/v1/plans/{slug}
plans:read

Um plano, pelo slug.

GET
/v1/addons
addons:read

Adicionais, com unidade e preço. O catálogo de adicionais é o mesmo para todas as contas.

Parâmetros de consulta

resource_typeusers | channels | storage_mb | ai_agent_conversations
includeinactive também lista os adicionais desativados.
limit, cursorPaginação por cursor.
GET
/v1/addons/{slug}
addons:read

Um adicional, pelo slug.

O que a API ainda não faz

Melhor ler aqui do que descobrir no meio da integração.

Orçamentos e negociações

A API não cria nem lê propostas e negociações. O aceite de uma proposta chega ao seu sistema pelo webhook quote.accepted.

Mensagens

Envia só texto, numa conversa que já existe. Não abre conversa nova, não envia mídia e não envia modelo aprovado do WhatsApp (HSM).

Contatos

POST /v1/contacts sempre cria: ele não procura duplicado por telefone nem por e-mail, então consulte antes com GET /v1/contacts?q=. Campos personalizados e etiquetas ficam fora da API, e o contato criado por ela não recebe registro de origem.

Pedidos

O pedido nasce confirmado, sem condição de pagamento e sem parcelas. A API não edita nem cancela pedido, e a leitura não traz pagamento, modo de entrega nem grupos de linha. O webhook order.created ainda sai sem os totais: para o valor, consulte GET /v1/orders/{id}.

Produtos

POST /v1/products cria e atualiza pela variação padrão: num produto com grade de variações, ele muda nome, descrição, categoria, unidade e situação, mas não o preço, o custo nem o SKU de cada variação. A grade e a base de venda por medida continuam sendo feitas na tela.

Ambiente de testes e SDK

Não há ambiente de testes separado: toda chamada age nos dados reais da empresa. Para explorar com segurança, use uma chave só de leitura. Não há SDK oficial; a especificação OpenAPI gera um cliente na sua linguagem.

Precisa de ajuda com a tela de chaves? O passo a passo está na Central de Ajuda.

Dúvidas de quem vai integrar

Faça a primeira chamada ainda hoje.

Teste grátis por 7 dias, com a API liberada. Crie a chave em Configurações, Integrações, API & Chaves e rode o curl do começo desta página.