Introdução
A API pública v1 permite que sistemas externos integrem com sua loja. Use-a para sincronizar produtos com seu ERP, importar pedidos, automatizar movimentações de estoque ou exportar clientes.
Base URL: https://katalog.com.br/api/v1
Autenticação
Todas as requisições devem incluir um Bearer token no header. Crie tokens no painel em /painel/api-tokens.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxO token está vinculado a uma loja específica. Não compartilhe seu token publicamente. Se for comprometido, revogue-o no painel e gere um novo.
Formato de resposta
Todas as respostas são JSON. Em caso de erro:
{
"erro": "Mensagem descritiva do erro"
}Códigos HTTP usados:
200— sucesso201— recurso criado400— erro de validação401— token inválido ou ausente404— recurso não encontrado500— erro interno
Produtos
GET /produtos — Listar produtos
Query params:
busca— filtra por nome, código de barras, SKU oucodIntegracao(quando numérico)categoriaId— filtra por categoriaapenasDisponiveis—truepara esconder os indisponíveispage,limite— paginação (default 1, 50)
Cada item da lista retorna também codIntegracao (Código ERP — inteiro identificador no sistema externo, ex.: Integreon) e as variações trazem o própriocodIntegracao. O produto também retorna referencia (texto livre até 80 caracteres, tipicamente a referência do fornecedor/fabricante).
URL pública: o campo url traz o link absoluto do produto no storefront (mesma URL do canonical/og:url) — formato https://{slug}.katalog.com.br/produto/{id} ou o domínio personalizado da loja quando configurado. Devolvido em GET /produtos, GET /produtos/[id], POST /produtos e PUT /produtos/[id].
curlcurl https://katalog.com.br/api/v1/produtos?limite=10 \ -H "Authorization: Bearer sk_live_..."
GET /produtos/[id] — Detalhes
curlcurl https://katalog.com.br/api/v1/produtos/PRODUTO_ID \ -H "Authorization: Bearer sk_live_..."
POST /produtos — Criar produto
Aceita codIntegracao (inteiro) para vincular o registro ao ID do produto no sistema externo (Integreon, ERP, etc). Se categoriaId não for enviado, o produto cai automaticamente em uma categoria "Sem categoria" criada na loja (uma única vez). Isso evita ter que fazer GET /categorias antes de cada importação.
Aceita também unidade (máx 10 chars, ex: UN, KG, L, M, PC; normalizado para maiúsculas; default PC) e multiploVenda (decimal > 0, default 1; ex: 10 para fardo de 10). Ambos podem ser editados depois via PUT.
Imagens externas: se você enviar imagens: ["https://outro-dominio/..."], baixamos cada URL e re-hospedamos no nosso S3 antes de gravar. Sem isso, o catálogo público bloqueia o domínio externo no <Image> do Next e mostra um quadrado vazio. URLs que já estão no nosso S3/CloudFront passam intactas.
curlcurl -X POST https://katalog.com.br/api/v1/produtos \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "nome": "Camiseta Branca", "preco": 49.90, "descricao": "100% algodão", "codigoBarras": "7891234567890", "sku": "CAM-BR-M", "referencia": "REF-FAB-A100", "codIntegracao": 1234, "controlaEstoque": true, "quantidadeEstoque": 100 }'
POST /produtos com upsert — Criar ou atualizar pelo codIntegracao
Envie "upsert": true (booleano) para o POST criar ou atualizar o produto pelo codIntegracao numa única chamada, junto com as variações. Sem essa chave o POST continua apenas criando, como descrito acima.
codIntegracaopassa a ser obrigatório: é a chave do upsert.- Produto novo: criado com as mesmas regras do POST (
nomeeprecoobrigatórios;controlaEstoquesó vale na criação). Resposta201com"operacao": "criado". - Produto existente: atualiza só os campos enviados, como o
PUT /produtos/[id]— semimagensas fotos ficam eimagens: []apaga; sem categoria a atual fica;controlaEstoqueé ignorado com aviso. Aceitacategoriapor nome. Resposta200com"operacao": "atualizado"e webhookproduto.atualizado. variacoes[]: cada item é identificado porcodIntegracao(ousku). Se já existe no produto, atualiza os campos enviados; senão, cria (valor1obrigatório). Variações que não vierem na lista não são apagadas. O estoque do produto é recalculado pela soma das variações.- Produto e variações são gravados juntos: se algum item falhar, nada é salvo.
- Havendo mais de um produto com o mesmo
codIntegracao, o alterado mais recentemente é atualizado e a resposta trazavisos.
curlcurl -X POST https://katalog.com.br/api/v1/produtos \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "upsert": true, "codIntegracao": 31, "nome": "Camiseta Básica", "preco": 49.90, "categoria": "Camisetas", "controlaEstoque": true, "variacoes": [ { "codIntegracao": 21136, "valor1": "AZUL", "valor2": "P", "quantidadeEstoque": 3 }, { "codIntegracao": 21138, "valor1": "AZUL", "valor2": "M", "quantidadeEstoque": 5 } ] }'
PUT /produtos/[id] — Atualizar
Atualiza campos parciais (envie só os que mudaram). Dispara o webhook produto.atualizado. Aceita:
nome,descricao,preco,precoPromocionalcategoriaId(validado: precisa pertencer à loja)imagens(array de URLs),sku,codigoBarrasreferencia— texto livre até 80 caracteres, referência do fornecedor/fabricante. Envienullpara limparcodIntegracao— Código ERP (inteiro). Envienullpara limpardisponivel,destaque,ordemquantidadeEstoque,estoqueMinimopeso(kg),altura,largura,comprimento(cm) — para frete em marketplaces
controlaEstoque não é aceito aqui. A flag pertence ao lojista: produto de preparo (hambúrguer, pizza, serviço) fica sem controle de propósito e vende infinito. Um ERP que reenviava controlaEstoque: true a cada sync religava o controle sozinho e o catálogo passava a barrar a venda com "Estoque insuficiente". Defina o valor na criação (POST /produtos); no PUT ele é ignorado e a resposta traz um texto em avisos[]. Pra espelhar saldo do ERP continue usando quantidadeEstoque ou POST /estoque.
curlcurl -X PUT https://katalog.com.br/api/v1/produtos/PRODUTO_ID \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "preco": 59.90, "quantidadeEstoque": 80, "peso": 0.250, "altura": 5, "largura": 30, "comprimento": 40 }'
Resposta 200: produto completo (mesmo formato do GET).
DELETE /produtos/[id] — Remover
Soft-delete: marca o produto como removido (deletadoEm = now() e disponivel = false). O produto somem dos GET da API mas permanece no histórico de pedidos. Idempotente — retorna 404 se já foi removido. Dispara o webhook produto.removido.
curlcurl -X DELETE https://katalog.com.br/api/v1/produtos/PRODUTO_ID \ -H "Authorization: Bearer sk_live_..."
{ "id": "PRODUTO_ID", "removido": true }Variações
Variações são as combinações de atributos de um produto (ex.: tamanho/cor). Cada variação tem seu próprio preço, estoque, SKU, código de barras e codIntegracao.
GET /produtos/[id]/variacoes — Listar variações do produto
curlcurl https://katalog.com.br/api/v1/produtos/PRODUTO_ID/variacoes \ -H "Authorization: Bearer sk_live_..."
POST /produtos/[id]/variacoes — Criar variação
curlcurl -X POST https://katalog.com.br/api/v1/produtos/PRODUTO_ID/variacoes \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "nome": "Tamanho M", "valor1": "M", "valor2": "Branco", "preco": 49.90, "sku": "CAM-BR-M", "codigoBarras": "7891234567891", "codIntegracao": 5678, "quantidadeEstoque": 30 }'
GET /variacoes/[id] — Detalhes
PUT /variacoes/[id] — Atualizar
Aceita os mesmos campos do POST (todos opcionais). Envie codIntegracao: nullpara limpar.
curlcurl -X PUT https://katalog.com.br/api/v1/variacoes/VARIACAO_ID \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "preco": 54.90, "codIntegracao": 9999 }'
DELETE /variacoes/[id] — Remover
Remoção definitiva (hard delete). Não há soft-delete para variações.
Complementos (Grupos)
Grupos de complemento são reutilizáveis (um mesmo grupo "Sabores" pode ser vinculado a vários produtos). Os itens do grupo são armazenados como JSON e cada item aceita nome, preco, estoque e codIntegracao.
GET /grupos-complemento — Listar grupos
curlcurl https://katalog.com.br/api/v1/grupos-complemento \ -H "Authorization: Bearer sk_live_..."
POST /grupos-complemento — Criar grupo
curlcurl -X POST https://katalog.com.br/api/v1/grupos-complemento \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "nome": "Sabores", "tipo": "SELECAO", "obrigatoria": true, "minimo": 1, "maximo": 1, "itens": [ { "nome": "Chocolate", "preco": 0, "codIntegracao": 11 }, { "nome": "Morango", "preco": 0, "codIntegracao": 12 }, { "nome": "Baunilha", "preco": 0, "codIntegracao": 13 } ] }'
Campos do body:
tipo—SELECAO(1 opção) ouMULTIPLA(várias)obrigatoria,minimo,maximo— regras de seleçãoitens— array de objetos{ nome, preco, estoque?, codIntegracao? }. OcodIntegracaodentro do item é o Código ERP daquele complemento.
GET /grupos-complemento/[id]
PUT /grupos-complemento/[id] — Atualizar
Se itens estiver presente, substitui completamente a lista anterior (não faz merge item-a-item). Reenvie a lista completa, com oscodIntegracao atualizados.
DELETE /grupos-complemento/[id] — Remover
Categorias
GET /categorias — Listar
Lista todas as categorias da loja.
POST /categorias — Criar
Cria categoria. nome é único por loja (case-insensitive). ordem é opcional — quando omitido, vai pro fim. Hierarquia Categoria → Subcategoria existe no schema mas é tratada em endpoint separado; categoriaPaiId no body é ignorado.
curlcurl -X POST https://katalog.com.br/api/v1/categorias \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "nome": "Hambúrgueres", "ordem": 1, "ativa": true }'
GET /categorias/[id] — Detalhes
PUT /categorias/[id] — Atualizar
Body (todos opcionais): nome, descricao, ordem, ativa. Renomear pra nome já usado por outra categoria retorna 400.
DELETE /categorias/[id] — Remover
Bloqueia (409) se a categoria tiver produtos ou subcategorias vinculados — sem isso, a cascata onDelete: Cascade apagaria os produtos junto. Realoque os produtos via PUT /produtos/[id] com novo categoriaId antes.
curlcurl -X DELETE https://katalog.com.br/api/v1/categorias/CAT_ID \ -H "Authorization: Bearer sk_live_..."
# 409 Conflict quando tem dependencia:
{ "erro": "Categoria tem 12 produto(s) vinculado(s). Realoque-os antes de remover.",
"produtosVinculados": 12 }Upsert de categoria no POST /produtos
Pra evitar 2 chamadas (criar categoria + criar produto), o POST /produtos aceita categoria (string) em vez de categoriaId. Se a categoria já existe (match case-insensitive), reusa; se não, cria. Ordem de prioridade no resolve:
categoriaIdexplícito (valida pertencimento à loja)categoria(string) → upsert por nome- Nenhum → cai em "Sem categoria" automática
curlcurl -X POST https://katalog.com.br/api/v1/produtos \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "nome": "X-Bacon", "preco": 28.90, "categoria": "Hambúrgueres" }'
Clientes
GET /clientes
Query params: busca, page, limite.
POST /clientes
Cria ou atualiza (upsert por documento). Campos:
{
"nome": "João da Silva",
"documento": "12345678900",
"tipoDocumento": "CPF",
"email": "joao@example.com",
"celular": "11999999999",
"cep": "01310100",
"endereco": "Av. Paulista",
"numero": "1000",
"bairro": "Bela Vista",
"cidade": "São Paulo",
"uf": "SP"
}Pedidos
GET /pedidos
Query params:
status— PENDENTE, CONFIRMADO, PREPARANDO, PRONTO, ENTREGUE, CANCELADOdesde— ISO date (filtro de criação >=)ate— ISO date (filtro de criação <=)page,limite
curlcurl "https://katalog.com.br/api/v1/pedidos?status=ENTREGUE&desde=2026-01-01" \ -H "Authorization: Bearer sk_live_..."
Cada item da lista retorna também pagamentos (array — o schema suporta split) e ajustePagamento (valor em R$ do ajuste % da forma). Estrutura de cada pagamento:{ formaPagamentoId, formaPagamento: "PIX" | "Dinheiro" | "Cartão de Crédito" | ..., valorPago }. Os mesmos campos aparecem em GET /pedidos/[id] e no payload do webhook pedido.criado.
Cada item já traz os dados do produto e da variação que um integrador precisa para mapear no ERP — não é preciso chamar GET /produtos/[id] para cada item:
"itens": [{
"id": "...",
"quantidade": 1,
"precoUnit": 28.00,
"produto": { "id": "...", "nome": "CAMISETA VERAO", "sku": "31", "codIntegracao": 31,
"codigoBarras": "789...", "imagens": ["https://..."], "unidade": "UN",
"quantidadeEstoque": 12 },
"variacao": { "id": "...", "nome": "AZUL / M", "valor1": "AZUL", "valor2": "M",
"sku": "25918", "codIntegracao": 25918, "codigoBarras": null,
"quantidadeEstoque": 3 }
}]Buscar o detalhe do produto item a item numa sincronização de pedidos multiplica as chamadas e estoura o limite por minuto. Use os campos acima; consulte GET /produtos/[id] só quando precisar de algo que o item não traz.
Lista e detalhe também trazem notasFiscais — array com o resumo das notas enviadas para o pedido (vazio se não houver). Veja Notas fiscais do pedido.
POST /pedidos — Criar pedido
Cria um pedido na loja. Preço é recalculado server-side (variação > promo > preço base) — o valor enviado no body é ignorado por segurança. Estoque é baixado em transação atômica: se um item não tem estoque, retorna 400 e nenhum item é baixado.
tipoEntrega—DELIVERY,BALCAO(retirada no balcão;RETIRADAtambém é aceito como alias) ouMESAcliente— objeto com snapshot. Secliente.idfor passado, valida que pertence à loja e usa o cadastro como fallback nos campos vaziositens[]— cada um comprodutoId,quantidade, opcionalvariacaoId,observacaoeopcoes(JSON de complementos)taxaEntrega,desconto,observacoes— opcionais
Dispara webhook pedido.criado ao sucesso. Não suporta cupons, cashback, mesas, múltiplas formas de pagamento, frete dinâmico, tabela de preço — use o painel pra esses fluxos.
Atenção ao múltiplo de venda: produtos com multiploVenda > 1 (ex: parafuso vendido de 10 em 10) têm a quantidade arredondada automaticamente para o próximo múltiplo. Pedir quantidade: 1 de um produto com multiploVenda: 10 grava 10 no pedido. Cheque esse campo no GET /produtos antes de enviar.
curlcurl -X POST https://katalog.com.br/api/v1/pedidos \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "tipoEntrega": "DELIVERY", "cliente": { "nome": "João da Silva", "celular": "11999999999", "email": "joao@example.com", "documento": "12345678900", "endereco": "Av. Paulista", "numero": "1000", "bairro": "Bela Vista", "cidade": "São Paulo", "uf": "SP", "cep": "01310100" }, "itens": [ { "produtoId": "PROD_ABC", "quantidade": 2 }, { "produtoId": "PROD_XYZ", "variacaoId": "VAR_123", "quantidade": 1, "observacao": "sem cebola" } ], "taxaEntrega": 8.50, "observacoes": "Entregar após 18h" }'
GET /pedidos/[id] — Detalhes
Retorna o pedido completo com itens, cliente e dados de rastreio.
PATCH /pedidos/[id] — Atualizar status / rastreio
Body (todos os campos exceto status são opcionais):
status—PENDENTE,CONFIRMADO,PREPARANDO,PRONTO,ENVIADO,ENTREGUEouCANCELADOmotivoCancelamento— obrigatório quandostatus = CANCELADOtempoEntrega— string livre (ex:"30-45 min")codigoRastreio,urlRastreio— para marketplaces / Correios
Comportamento automático:
- Ao mudar
PENDENTE → CONFIRMADO: baixa estoque dos itens - Ao cancelar pedido confirmado: restaura estoque
- Sempre dispara webhook
pedido.status - Se
status = CANCELADO: também disparapedido.cancelado
curlcurl -X PATCH https://katalog.com.br/api/v1/pedidos/PEDIDO_ID \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "status": "ENVIADO", "codigoRastreio": "BR123456789", "urlRastreio": "https://rastreamento.correios.com.br/...", "tempoEntrega": "3-5 dias úteis" }'
DELETE /pedidos/[id] — Apagar pedido
Apaga o pedido em definitivo (itens e pagamentos vão junto). Irreversível.
Só apaga pedido cancelado. Se o pedido estiver em qualquer outro status, a resposta é 409 — cancele antes (PATCH comstatus: "CANCELADO"). Cancelar mantém o pedido no histórico; apagar o remove.
curlcurl -X DELETE https://katalog.com.br/api/v1/pedidos/PEDIDO_ID -H "Authorization: Bearer sk_live_..."
{
"ok": true,
"numero": 774,
"mensagem": "Pedido #774 apagado permanentemente."
}Notas fiscais do pedido
O ERP (ou um integrador, como o Integreon) envia a nota fiscal emitida para o pedido. É o mesmo modelo do Mercado Livre: você manda o XML autorizado e o Katalog lê do próprio arquivo a chave de acesso, número, série, modelo, data de emissão, valor, emitente, destinatário e protocolo. A nota aparece no gerenciamento do pedido no painel, com download do XML e do DANFE — e o lojista também pode anexar por lá.
Envie sempre os dois arquivos: o XML e o PDF (DANFE). O XML é o documento fiscal; o PDF é o que o lojista consegue abrir e imprimir. O Katalog guarda e devolve os arquivos que você manda — não gera DANFE nem espelho da nota, porque não é emissor fiscal. Quem produz o DANFE é o seu emissor.
Mandar só o XML não dá erro, mas a resposta vem com o aviso "Nota sem DANFE em PDF" e o lojista fica sem conseguir visualizar a nota no painel. Se o PDF só existir depois, envie-o depois com o campo chave — ele é anexado à mesma nota.
POST /pedidos/[id]/notas-fiscais — Enviar nota
Três formatos, use o mais simples para o seu sistema:
1. XML cru no corpo (como o invoice_data do Mercado Livre):
curlcurl -X POST https://katalog.com.br/api/v1/pedidos/PEDIDO_ID/notas-fiscais \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/xml" \ --data-binary @53260912345678000195550010000012341123456786-procNFe.xml
2. multipart/form-data — XML e DANFE juntos nos campos xml e pdf. O campo fiscal_document do Mercado Livre também é aceito (até um XML e um PDF, tipo detectado pelo conteúdo):
curlcurl -X POST https://katalog.com.br/api/v1/pedidos/PEDIDO_ID/notas-fiscais \ -H "Authorization: Bearer sk_live_..." \ -F "xml=@nota.xml" \ -F "pdf=@danfe.pdf"
3. JSON — quando o XML já está em memória (ex.: ERP em Delphi):
{
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><nfeProc ...>...</nfeProc>",
"pdfBase64": "JVBERi0xLjcK..." // opcional
}No JSON, xmlBase64 substitui xml. Sem XML nenhum, dá para registrar só os metadados com { "chave", "numero", "serie" } — a chave é validada pelo dígito verificador.
Resposta (201 ao criar, 200 ao atualizar ou cancelar):
{
"acao": "criada", // criada | atualizada | cancelada
"id": "cmf1x...",
"chave": "53260912345678000195550010000012341123456786",
"numero": "1234",
"serie": "1",
"modelo": "55", // 55 NF-e, 65 NFC-e
"situacao": "AUTORIZADA", // AUTORIZADA | CANCELADA
"dataEmissao": "2026-09-11T20:50:00.000Z",
"valorTotal": 37.04,
"cnpjEmitente": "12345678000195",
"documentoDestinatario": "12345678909",
"protocolo": "353260000123456",
"canceladaEm": null,
"temXml": true,
"temPdf": true,
"temXmlCancelamento": false,
"origem": "API",
"criadoEm": "2026-09-11T21:02:13.000Z",
"atualizadoEm": "2026-09-11T21:02:13.000Z",
"avisos": []
}Regras:
- Idempotente pela chave de acesso. Reenviar a mesma nota responde
200comacao: "atualizada"— não duplica. Só os arquivos enviados são substituídos; para mandar o DANFE depois, envie opdfcom o campochave. - Só nota autorizada (cStat 100 ou 150 no
protNFe). Nota denegada ou rejeitada volta400. XML sem protocolo é aceito, mas com aviso — prefira onfeProc. - Cancelamento: envie ao mesmo endpoint o XML do evento (
procEventoNFe, tipo 110111 ou 110112, homologado pela SEFAZ). A nota viraCANCELADAe a resposta trazacao: "cancelada". Carta de correção e outros eventos são recusados. avisos[]lista inconsistências que não bloqueiam: emitente diferente do CNPJ da loja, destinatário diferente do documento do cliente do pedido, valor da nota diferente do total, XML sem protocolo, nota de homologação. Vale registrar no log do integrador.- O status do pedido não muda — use
PATCH /pedidos/[id]se quiser marcar como enviado. - Os arquivos não têm URL pública (o XML traz dados pessoais do cliente): baixe pela API.
- Limites: XML 2 MB, PDF 5 MB, 20 notas por pedido.
- Dispara o webhook
pedido.nota_fiscal.
Erros:
400— XML não é NF-e/NFC-e, nota não autorizada, chave inválida ou diferente da do XML, arquivo vazio ou PDF inválido400— XML de transporte: lote de envio (enviNFe), retorno do lote, consulta de situação, inutilização ou envelope SOAP. São arquivos que o emissor gera no caminho da autorização, mas não são a nota. A mensagem diz qual é o arquivo e o que enviar no lugar. Atenção aoenviNFe: ele contém a nota, mas sem protocolo — é a versão anterior à autorização, e por isso é recusado404— pedido não encontrado, ou cancelamento de uma nota que ainda não foi enviada409— a chave já está vinculada a outro pedido da loja, ou o pedido já tem 20 notas413— arquivo acima do limite415— Content-Type diferente de XML, multipart ou JSON
NFS-e (nota de serviço)
Além de NF-e e NFC-e, o mesmo endpoint aceita NFS-e no padrão ABRASF (o XML que começa com <CompNfse>, gerado pela maioria das prefeituras). Mande do mesmo jeito — XML cru, multipart ou JSON — que o Katalog identifica o layout sozinho.
- Não existe chave de acesso em NFS-e. A identidade é
numero+codigoVerificacaodentro do CNPJ do prestador. O campochavevoltanulle a idempotência do reenvio usa essa combinação — continua sem duplicar. modelovem"56"eserievemnull(oSeriedo XML é do RPS que gerou a nota, não da NFS-e).- Cancelamento: a NFS-e cancelada traz o bloco
<NfseCancelamento>no próprio XML, então basta enviar o arquivo — a nota entra comoCANCELADA, com a data do cancelamento. Não existe XML de evento separado como na NF-e. - Prestador e tomador entram em
cnpjEmitenteedocumentoDestinatario(o tomador pode ser CPF ou CNPJ), e os mesmosavisos[]de divergência valem.
curlcurl -X POST https://katalog.com.br/api/v1/pedidos/PEDIDO_ID/notas-fiscais \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/xml" \ --data-binary @nfse.xml
{
"acao": "criada",
"chave": null,
"numero": "1445",
"serie": null,
"modelo": "56",
"codigoVerificacao": "21473A803",
"situacao": "AUTORIZADA",
"valorTotal": 2658.00,
"cnpjEmitente": "02887418000198",
"documentoDestinatario": "26516381000150"
}GET /pedidos/[id]/notas-fiscais — Listar notas
{ "notasFiscais": [ { "id": "...", "chave": "...", "numero": "1234", "situacao": "AUTORIZADA", ... } ] }GET /pedidos/[id]/notas-fiscais/[notaId] — Detalhes ou arquivo
Sem query devolve o JSON da nota. Com ?arquivo=xml, pdf ou xml-cancelamento devolve o arquivo.
curlcurl "https://katalog.com.br/api/v1/pedidos/PEDIDO_ID/notas-fiscais/NOTA_ID?arquivo=xml" \ -H "Authorization: Bearer sk_live_..." -o nota.xml
Acrescente &inline=1 para receber o arquivo com Content-Disposition: inline — o navegador abre no visualizador em vez de baixar. É o que o painel usa no botão Ver / Imprimir do DANFE. Sem o parâmetro, o arquivo vem como anexo.
DELETE /pedidos/[id]/notas-fiscais/[notaId] — Remover nota
Desvincula a nota do pedido e apaga os arquivos. Não cancela nada na SEFAZ — para registrar cancelamento, envie o XML do evento no POST.
Etiqueta de envio do pedido
O Katalog gera a etiqueta do pedido em PDF, no molde das de Nuvemshop, Mercado Livre e Shopee:
- número do pedido em destaque, com código de barras Code 128 (o valor lido é o
numerodo pedido — curto, fácil de bipar na expedição); - QR Code com o link de acompanhamento do pedido;
- destinatário com CEP em destaque, telefone e referência;
- forma de entrega, código de rastreio e a nota fiscal autorizada vinculada, se houver;
- peso — só quando todos os itens têm peso cadastrado (peso parcial engana quem despacha);
- volume N/total — uma etiqueta por caixa;
- conteúdo (itens com a grade) e remetente (dados da loja).
É a etiqueta da loja: serve para entrega própria, motoboy, retirada e para identificar volumes. Envio por transportadora (Correios, Jadlog etc.) continua usando a etiqueta oficial da transportadora.
GET /pedidos/[id]/etiqueta — Etiqueta de um pedido
curlcurl "https://katalog.com.br/api/v1/pedidos/PEDIDO_ID/etiqueta?tamanho=10x15&volumes=2" \ -H "Authorization: Bearer sk_live_..." \ -o etiqueta.pdf
tamanho—10x15(padrão): uma etiqueta por página, 100×150 mm, para impressora térmica.a4: 4 etiquetas por folha, com linha de corte.volumes— de 1 a 50 (padrão 1). Gera uma etiqueta por volume:1/2,2/2.itens=0— esconde a lista de conteúdo.inline=0— devolve como anexo. Por padrão o PDF veminline, para abrir e imprimir.formato=zpl— em vez de PDF, devolve ZPL, a linguagem nativa das impressoras térmicas Zebra (e compatíveis). A impressora desenha código de barras e QR na própria resolução: sai mais nítido e dá para mandar direto para a impressora, sem abrir visualizador. Só comtamanho=10x15; vem como anexo.zpl, um bloco^XA…^XZpor volume.dpi— resolução da impressora para o ZPL:203(padrão, 8 dots/mm) ou300.
curl# ZPL direto para a Zebra compartilhada na rede (porta 9100) curl "https://katalog.com.br/api/v1/pedidos/PEDIDO_ID/etiqueta?formato=zpl&volumes=2" \ -H "Authorization: Bearer sk_live_..." -o etiqueta.zpl nc 192.168.0.50 9100 < etiqueta.zpl
Erros: 400 tamanho, formato, dpi ou volumes inválido · 404 pedido não encontrado na loja.
POST /etiquetas/lote — Várias etiquetas num PDF
Para a expedição imprimir tudo de uma vez. As etiquetas saem na ordem enviada.
POST /api/v1/etiquetas/lote
{
"pedidos": [
"k7qm2xpa9r4t",
{ "id": "m3tq8wz2kd5p", "volumes": 3 }
],
"tamanho": "10x15", // 10x15 | a4
"formato": "pdf", // pdf | zpl (zpl só 10x15)
"volumes": 1, // padrão para quem não informou o seu
"itens": true
}- Resposta:
application/pdf(anexo;?inline=1abre no navegador) ou, comformato: "zpl", um.zplcom todas as etiquetas. - Se algum pedido não existir na loja, o lote inteiro é recusado com
404e a lista emnaoEncontrados— melhor saber agora do que descobrir um volume sem etiqueta na hora de despachar. - Limites: 100 pedidos e 200 etiquetas (soma dos volumes) por requisição.
Etiqueta da transportadora
Quando o seu sistema compra o frete (Correios, Jadlog, Loggi, etiqueta de marketplace…), envie a etiqueta oficial para o pedido — igual à nota fiscal: o Katalog não compra frete nem gera etiqueta de transportadora, ele guarda o arquivo que você manda, mostra ao lojista no painel (Ver / Imprimir) e devolve pela API. Aceita PDF ou ZPL, com o tipo detectado pelo conteúdo.
POST /pedidos/[id]/etiquetas — Enviar etiqueta
curlcurl -X POST https://katalog.com.br/api/v1/pedidos/PEDIDO_ID/etiquetas \ -H "Authorization: Bearer sk_live_..." \ -F "arquivo=@etiqueta-correios.pdf" \ -F "transportadora=Correios" \ -F "servico=PAC" \ -F "codigoRastreio=AA123456789BR" \ -F "urlRastreio=https://rastreamento.correios.com.br/app/index.php?objetos=AA123456789BR" \ -F "volume=1"
Também aceita JSON (pdfBase64, zplBase64 ou zpl em texto, mais os mesmos campos) e o arquivo cru no corpo (application/pdf ou text/plain para ZPL), com os campos na query. Resposta (201 criada, 200 substituída):
{
"acao": "criada", // criada | atualizada
"id": "cmf9x...",
"transportadora": "Correios",
"servico": "PAC",
"codigoRastreio": "AA123456789BR",
"urlRastreio": "https://rastreamento.correios.com.br/...",
"volume": 1,
"formato": "PDF", // PDF | ZPL
"tamanhoBytes": 48211,
"origem": "API",
"criadoEm": "2026-09-14T21:30:00.000Z",
"atualizadoEm": "2026-09-14T21:30:00.000Z",
"avisos": []
}Regras:
- Idempotente pelo
codigoRastreiodentro do pedido: reenviar com o mesmo código substitui o arquivo em vez de duplicar. Sem código, cada envio cria uma etiqueta nova — a resposta avisa. - Um pedido com vários volumes pode ter uma etiqueta por volume, cada uma com o seu código.
- Se o pedido ainda não tem código de rastreio, o enviado passa a ser o do pedido (o que o cliente vê ao acompanhar). Código já preenchido não é sobrescrito. O status do pedido não muda.
- O arquivo não tem URL pública (traz nome e endereço do cliente): baixe pela API.
- Limites: PDF 5 MB, ZPL 1 MB, 50 etiquetas por pedido. Dispara o webhook
pedido.etiqueta.
Erros: 400 sem arquivo, arquivo que não é PDF nem ZPL, volume ou urlRastreio inválido · 404 pedido não encontrado · 409 limite de etiquetas · 413 arquivo grande · 415 Content-Type não suportado.
GET /pedidos/[id]/etiquetas — Listar
Devolve { etiquetas: [...] }. As etiquetas também vêm em etiquetasEnvio no GET de pedidos.
GET /pedidos/[id]/etiquetas/[etiquetaId] — Detalhes ou arquivo
Sem query, o JSON. Com ?arquivo=1, o arquivo (PDF ou ZPL); &inline=1 abre o PDF no navegador.
DELETE /pedidos/[id]/etiquetas/[etiquetaId] — Remover
Apaga a etiqueta e o arquivo. Não cancela o envio na transportadora.
Estoque
POST /estoque — Movimentação
Registra movimentação de estoque (entrada, saída ou ajuste).
{
"produtoId": "PRODUTO_ID",
"tipo": "ENTRADA", // ou "SAIDA" ou "AJUSTE"
"quantidade": 50,
"variacaoId": null, // opcional
"observacao": "Compra do fornecedor X"
}- ENTRADA: soma à quantidade atual
- SAIDA: subtrai da quantidade atual
- AJUSTE: define a quantidade absoluta
Exemplo: importar produtos do ERP
javascriptconst TOKEN = "sk_live_..."; const BASE = "https://katalog.com.br/api/v1"; async function criarProduto(produto) { const res = await fetch(`${BASE}/produtos`, { method: "POST", headers: { "Authorization": `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(produto), }); if (!res.ok) { const err = await res.json(); throw new Error(err.erro); } return res.json(); } // Loop sobre os produtos do ERP for (const item of produtosDoERP) { await criarProduto({ nome: item.descricao, preco: item.precoVenda, codigoBarras: item.ean, sku: item.codigo, controlaEstoque: true, quantidadeEstoque: item.estoqueAtual, }); }
Limites
Limite atual: 180 requisições por minuto por token, das quais até 90 podem ser de escrita (POST, PUT, PATCH, DELETE). A cota é de cada token — duas lojas integradas pelo mesmo sistema não dividem limite. Para volume alto, prefira os endpoints em lote, como POST /estoque/lote. Em caso de excesso, retornamos 429 Too Many Requests com os headers:
X-RateLimit-Limit— limite total por minuto (180)X-RateLimit-Remaining— quantas restam na janelaX-RateLimit-Reset— segundos até resetarRetry-After— segundos sugeridos antes de tentar de novo
Recursos extras
- SDKs prontos — JavaScript, Python e PHP
- OpenAPI Spec — Swagger UI + Postman
- Status — disponibilidade dos serviços
- Gerenciar tokens — criar, revogar, métricas
Webhooks de saída
Receba notificações em uma URL externa quando eventos ocorrerem na sua loja. Pode configurar pelo painel em /painel/webhooks ou via API (abaixo).
Eventos disponíveis
pedido.criado— novo pedido recebidopedido.status— status do pedido mudoupedido.pago— pagamento confirmadopedido.cancelado— pedido canceladopedido.nota_fiscal— nota fiscal do pedido foi criada, atualizada, cancelada ou removida (pela API ou pelo painel).dados={ acao, pedidoId, pedidoNumero, notaFiscal }pedido.etiqueta— etiqueta de transportadora criada, substituída ou removida (pela API ou pelo painel).dados={ acao, pedidoId, pedidoNumero, etiqueta }pedido.editado— a loja alterou itens, quantidades, preços, desconto ou frete do pedido no painel.dados={ id, numero, status, resumo, antes, depois, editadoPor, editadoEm }(antes/depoissão snapshots comitens,subtotal,ajusteTabela,desconto,taxaEntrega,ajustePagamentoetotal). Ao receber, refaçaGET /pedidos/{id}para pegar o pedido atualizado (itens e totais).cliente.criado— novo cliente cadastradoproduto.atualizado— produto foi alterado (PUT)produto.removido— produto foi removido (DELETE)produto.estoque_baixo— produto atingiu o mínimo
POST /webhooks — Cadastrar webhook
Cria um webhook scoped à loja autenticada. O secret é gerado pelo servidor (formato whsec_<48 hex>) e retornado apenas neste response — guarde-o, não há como recuperá-lo depois.
curlcurl -X POST https://katalog.com.br/api/v1/webhooks \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "nome": "Integreon", "url": "https://hub.integreon.com.br/webhooks/meucatalogo", "eventos": ["pedido.criado", "produto.atualizado", "produto.removido"] }'
{
"id": "...",
"nome": "Integreon",
"url": "https://...",
"eventos": ["pedido.criado", "produto.atualizado", "produto.removido"],
"ativo": true,
"secret": "whsec_abc123...",
"criadoEm": "2026-04-27T..."
}GET /webhooks — Listar
Retorna os webhooks da loja (sem expor o secret).
DELETE /webhooks/[id] — Remover
curlcurl -X DELETE https://katalog.com.br/api/v1/webhooks/WEBHOOK_ID \ -H "Authorization: Bearer sk_live_..."
Formato do payload
POST com Content-Type application/json e os headers:
X-MeuCatalogo-Event: pedido.criado
X-MeuCatalogo-Signature: sha256=<hex>
Content-Type: application/jsonBody:
{
"evento": "pedido.criado",
"criadoEm": "2026-04-22T15:30:00Z",
"dados": {
"id": "...",
"numero": 123,
"status": "PENDENTE",
"tipoEntrega": "RETIRADA",
"subtotal": 100.00,
"desconto": 0,
"ajustePagamento": -5.00,
"taxaEntrega": 0,
"total": 95.00,
"observacoes": null,
"pagamentoStatus": "PENDENTE",
"pagamentos": [
{ "formaPagamentoId": "...", "formaPagamento": "PIX", "valorPago": 95.00 }
],
"clienteNome": "Fulano",
"clienteCelular": "5561999999999",
"clienteEmail": "fulano@example.com",
"clienteDocumento": "12345678901",
"clienteEndereco": "...",
"criadoEm": "2026-04-22T15:30:00Z"
}
}pagamentos é um array — o schema suporta split (PIX + Dinheiro, etc). Cada item traz a descrição canônica da forma (Dinheiro, PIX, Cartão de Crédito, etc.) para você mapear pra sua tabela. ajustePagamento em reais reflete o ajuste % da forma (negativo = desconto no PIX, positivo = acréscimo no cartão).
Validar a assinatura HMAC
Use o secret mostrado no momento da criação do webhook. Calcule HMAC SHA-256 do body com o secret e compare com o header X-MeuCatalogo-Signature.
javascriptimport crypto from "crypto"; function verificarAssinatura(secret, body, assinatura) { const hmac = crypto.createHmac("sha256", secret); hmac.update(body); const esperada = "sha256=" + hmac.digest("hex"); return crypto.timingSafeEqual( Buffer.from(esperada), Buffer.from(assinatura) ); }
Retentativas e timeout
Cada entrega tem timeout de 10 segundos. Sua URL deve responder com status 2xx para ser considerada sucesso.
Em caso de falha, retentamos com backoff exponencial: +30s, +2min, +10min, +1h (total de 5 tentativas). O header X-MeuCatalogo-Tentativa indica o número da tentativa.
Logs
Visualize histórico de entregas em /painel/webhooks. Cada log mostra: payload enviado, status HTTP recebido, resposta e duração.
Suporte
Encontrou um bug ou precisa de um endpoint que ainda não existe? Fale com nosso time pelo WhatsApp.