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.
https://staging.codewo.com.br/api/v1120/min por chave
600/min por empresa
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"{
"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.
| Recurso | Método | Caminho | Permissão |
|---|---|---|---|
| Saúde | GET | /v1/health | sem chave |
| Identidade | GET | /v1/me | qualquer chave |
| Usuários | GET | /v1/users | users:read |
| Usuários | GET | /v1/users/{id} | users:read |
| Canais | GET | /v1/channels | channels:read |
| Canais | GET | /v1/channels/{id} | channels:read |
| Conversas | GET | /v1/conversations | conversations:read |
| Conversas | GET | /v1/conversations/{id} | conversations:read |
| Mensagens | GET | /v1/conversations/{id}/messages | messages:read |
| Mensagens | POST | /v1/conversations/{id}/messages | messages:write |
| Contatos | GET | /v1/contacts | contacts:read |
| Contatos | GET | /v1/contacts/{id} | contacts:read |
| Contatos | POST | /v1/contacts | contacts:write |
| Contatos | PATCH | /v1/contacts/{id} | contacts:write |
| Contatos | DELETE | /v1/contacts/{id} | contacts:delete |
| Produtos | GET | /v1/products | products:read |
| Produtos | GET | /v1/products/{id} | products:read |
| Produtos | POST | /v1/products | products:write |
| Pedidos | GET | /v1/orders | orders:read |
| Pedidos | GET | /v1/orders/{id} | orders:read |
| Pedidos | POST | /v1/orders | orders:write |
| Templates | GET | /v1/templates/messages | templates:read |
| Templates | GET | /v1/templates/messages/{id} | templates:read |
| Templates | GET | /v1/templates/hsm | templates:read |
| Templates | GET | /v1/templates/hsm/{id} | templates:read |
| Planos | GET | /v1/plans | plans:read |
| Planos | GET | /v1/plans/{slug} | plans:read |
| Adicionais | GET | /v1/addons | addons:read |
| Adicionais | GET | /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ão | Tipo | O que libera |
|---|---|---|
conversations:readConversas | leitura | Listar e ver conversas. |
conversations:writeConversas | escrita | Reservado. Nenhum endpoint usa hoje: a API não altera conversa. |
messages:readMensagens | leitura | Ler o histórico de mensagens de uma conversa. |
messages:writeMensagens | escrita | Enviar mensagem de texto numa conversa. |
contacts:readContatos | leitura | Listar e ver contatos, com o dono da carteira. |
contacts:writeContatos | escrita | Criar e editar contatos, inclusive a carteira. |
contacts:deleteContatos | exclusão | Excluir contatos. |
channels:readCanais | leitura | Listar os canais conectados. |
users:readUsuários | leitura | Listar a equipe (é daqui que sai o sender_id). |
templates:readTemplates | leitura | Templates de mensagem e modelos aprovados do WhatsApp (HSM). |
plans:readPlanos | leitura | Catálogo de planos visível para a conta da chave. |
addons:readAdicionais | leitura | Catálogo de adicionais. |
products:readCatálogo | leitura | Ler o catálogo de produtos da empresa. |
products:writeCatálogo | escrita | Criar e atualizar produtos (pelo SKU). |
orders:readVendas | leitura | Ler pedidos, com linhas, totais e entrega. |
orders:writeVendas | 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).{
"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.{
"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=25Limite 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-LimitLimite do balde mais apertado na janela de um minuto.
X-RateLimit-RemainingQuantas chamadas ainda cabem nesse balde.
X-RateLimit-ResetMomento (Unix) em que a janela reinicia.
X-RateLimit-ScopeQual balde está mais apertado: key (a chave) ou company (a empresa).
Retry-AfterSó no 429: quantos segundos esperar antes de tentar de novo.
X-Request-IdIdentificador ú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.
{
"error": {
"code": "insufficient_scope",
"message": "This API key does not have the required scope: orders:write.",
"required_scope": "orders:write"
}
}{
"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."]
}
}
}{
"error": {
"code": "subscription_inactive",
"message": "Your subscription is not active. Reactivate to use the API.",
"subscription_status": "suspended"
}
}| HTTP | code | Significado |
|---|---|---|
401 | missing_api_key | Cabeçalho Authorization ausente. |
401 | invalid_api_key | A chave não existe ou está mal formada. |
401 | revoked_api_key | A chave foi revogada. |
401 | expired_api_key | A chave passou da data de expiração. |
402 | subscription_inactive | A 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_allowed | O IP de origem não está na lista de IPs permitidos da chave. |
403 | insufficient_scope | A chave não tem a permissão que o endpoint exige (required_scope diz qual). |
404 | not_found | O 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_failed | Dados inválidos. details traz os erros por campo. |
422 | channel_inactive | O canal da conversa está desativado. |
422 | channel_disconnected | Canal de WhatsApp, Telegram, Instagram ou Messenger desconectado. Reconecte antes de enviar. |
429 | rate_limited | Passou do limite por minuto. Retry-After diz quanto esperar. |
500 | internal_error | Erro inesperado do nosso lado. |
503 | service_unavailable | Serviço indisponível no momento. Tente de novo em instantes. |
Identidade e saúde
/v1/meDevolve 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.
{
"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"
}/v1/healthVerificaçã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.
{
"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.
/v1/usersLista 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.{
"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.
/v1/users/{id}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.
/v1/channelsLista os canais da empresa, ativos e inativos.
Parâmetros de consulta
typewhatsapp (oficial) | whatsapp_web | instagram | facebook (Messenger) | telegram | email | webchat | smsis_activetrue | falselimit, cursorPaginação por cursor.{
"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
}/v1/channels/{id}Um canal.
Conversas
As conversas da Caixa de Entrada, com o contato junto. Só leitura: a API não muda status nem atribuição.
/v1/conversationsLista as conversas da empresa.
Parâmetros de consulta
statusopen | pending | in_progress | resolved | spam | snoozed (adiada)prioritylow | medium | high | urgentassigned_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.{
"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
aitraz a análise da IA da conversa, quando houver.
/v1/conversations/{id}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.
/v1/conversations/{id}/messagesHistó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_typediz 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
contentnulo. channel_typeedirectionvêm na resposta, mas nulos: o canal está em channel_id, e quem enviou, em sender_type.
/v1/conversations/{id}/messagesEnvia 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.
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.{
"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.
/v1/contactsLista 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 | blockedtypepf | pjsourceO 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./v1/contacts/{id}Um contato, com endereço, origem e dono da carteira.
/v1/contactsCria um contato. Sempre cria: não procura duplicado por telefone nem e-mail.
{
"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 | blockedsource 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.{
"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"
}
}/v1/contacts/{id}Atualiza só os campos enviados. Mandar assigned_to troca o dono da carteira; mandar null tira o dono.
{
"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.
/v1/contacts/{id}Exclui o contato. O histórico de conversas continua guardado.
{ "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.
/v1/productsLista 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 | archivedcategory_idProdutos de uma categoria.page, per_pagePaginação por página (padrão 25, máximo 100)./v1/products/{id}Um produto, com categoria e variações.
/v1/productsCria 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.
{
"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.{
"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.
/v1/ordersLista os pedidos, com linhas, totais em centavos e situação da entrega.
Parâmetros de consulta
statusdraft | confirmed | cancelledcontact_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)./v1/orders/{id}Um pedido, com contato, entrega, totais e linhas.
/v1/ordersRegistra 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.
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{
"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.{
"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
Oexternal_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.
/v1/templates/messagesTemplates de mensagem da equipe.
Parâmetros de consulta
qBusca no nome, atalho e conteúdo.categoryCategoria do template.is_activetrue | falselimit, cursorPaginação por cursor./v1/templates/messages/{id}Um template de mensagem.
/v1/templates/hsmModelos aprovados do WhatsApp oficial, com componentes e situação na Meta.
Parâmetros de consulta
statusAPPROVED | PENDING | REJECTED | DISABLED | PAUSEDlanguagept_BR, en_US...channel_idModelos de um canal.qBusca no nome.limit, cursorPaginação por cursor./v1/templates/hsm/{id}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.
/v1/plansPlanos 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./v1/plans/{slug}Um plano, pelo slug.
/v1/addonsAdicionais, 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_conversationsincludeinactive também lista os adicionais desativados.limit, cursorPaginação por cursor./v1/addons/{slug}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.