Testar com segurança
A API Cotejar tem um único ambiente: https://api.cotejar.com.br. Não há sandbox nem base de testes com dados fictícios. Isso é intencional: uma regra regulatória testada contra dados inventados não prova nada sobre o seu catálogo.
O que existe é um protocolo em que nada muda sem confirmação explícita. As camadas abaixo vão de zero efeito a efeito controlado. Suba de uma para a próxima só quando a anterior passar.
| Camada | O que prova | Altera o catálogo? |
|---|---|---|
| 1. Leituras | Chave, empresa e permissões | Não |
| 2. Erros provocados | Tratamento de 401, 422, 428 e 412 |
Não |
| 3. Ingestão sem aplicar | Formato dos lotes, NCMs, recusas e idempotência | Não |
| 4. Webhooks | Receptor, assinatura e deduplicação | Não |
| 5. CSV de exemplo | Validação de tamanho, hash e estrutura | Não |
| 6. Primeira aplicação | O fluxo completo | Sim, de forma controlada |
1. Leituras
Seção intitulada “1. Leituras”Leituras não têm efeito e mostram se a chave está na empresa certa.
curl -sS -H "Authorization: Bearer $COTEJAR_API_KEY" "$COTEJAR_API_BASE_URL/v1/conta"curl -sS -H "Authorization: Bearer $COTEJAR_API_KEY" "$COTEJAR_API_BASE_URL/v1/catalogos"Confira cnpj_raiz e escopos em /v1/conta, e anote o id do catálogo que vai receber os lotes.
2. Erros provocados
Seção intitulada “2. Erros provocados”Provoque cada recusa de propósito e confirme que o seu código registra o request_id e não trata a falha como sucesso. Nenhuma destas chamadas muda dados.
curl -sS -H "Authorization: Bearer ctj_api_invalida" "$COTEJAR_API_BASE_URL/v1/conta"{ "erro": { "codigo": "CREDENCIAL_INVALIDA", "mensagem": "credencial ausente, inválida, expirada ou revogada", "request_id": "b0b071f7-f965-443d-a0a4-4ad20a9219c2" } }curl -sS -X POST "$COTEJAR_API_BASE_URL/v1/catalogos/ingestoes" \ -H "Authorization: Bearer $COTEJAR_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"catalogo_id":"nao-e-uuid","operacao":"mesclar"}'{ "erro": { "codigo": "REQUISICAO_INVALIDA", "mensagem": "A requisição não corresponde ao contrato da API.", "request_id": "634459f3-6f2b-420d-b613-9ad875826eec" } }curl -sS -X PATCH "$COTEJAR_API_BASE_URL/v1/catalogos/$CATALOGO_ID" \ -H "Authorization: Bearer $COTEJAR_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"nome":"Catálogo da filial"}'{ "erro": { "codigo": "VERSAO_OBRIGATORIA", "mensagem": "Confira o formato, tamanho e versão da requisição.", "request_id": "f1ffe5f7-353b-4ddc-ac9c-fdd90efec9fd" } }Exige catalogo:gerenciar. A edição só acontece com o ETag atual em If-Match.
curl -sS -X PATCH "$COTEJAR_API_BASE_URL/v1/catalogos/$CATALOGO_ID" \ -H "Authorization: Bearer $COTEJAR_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H 'If-Match: "catalogo-1-0000000000000000000000000000000000000000000000000000000000000000"' \ -H "Content-Type: application/json" \ -d '{"nome":"Catálogo da filial"}'{ "erro": { "codigo": "VERSAO_DIVERGENTE", "mensagem": "O recurso mudou. Consulte-o novamente antes de editar.", "request_id": "e2c98e8f-2702-41e0-95d4-42464e4eef8c" } }O If-Match acima é um ETag bem formado e deliberadamente antigo, só para o teste. Na integração, trate o ETag como opaco e envie sempre o valor completo devolvido pela última leitura.
O 412 é uma decisão para o seu sistema: leia o recurso de novo e confirme se a alteração ainda faz sentido. Não repita automaticamente.
3. Ingestão sem aplicar
Seção intitulada “3. Ingestão sem aplicar”A ingestão tem três momentos: receber lotes, validar e aplicar. Só aplicar altera o catálogo. Até a validação, você exercita o formato real dos seus dados, com as NCMs reais, sem efeito nenhum sobre o que já está cadastrado.
-
Inicie a ingestão no catálogo anotado na camada 1.
201 Created { "ingestao_id": "5306d957-04d7-44dd-9c63-e4d04d2e19fe", "estado": "recebendo_lotes", "repetida": false, "catalogo_id": "7c7b354f-07b6-4461-9240-263418c81cdb", "versao_catalogo": 1 } -
Envie um lote com produtos reais do ERP e repita o mesmo pedido com a mesma
Idempotency-Key. O segundo envio prova que um timeout no seu lado não duplica o lote.{ "ingestao_id": "5306d957-04d7-44dd-9c63-e4d04d2e19fe", "sequencia": 1, "produtos_recebidos": 3, "repetido": false }{ "ingestao_id": "5306d957-04d7-44dd-9c63-e4d04d2e19fe", "sequencia": 1, "produtos_recebidos": 3, "repetido": true }{ "erro": { "codigo": "LOTE_CONFLITANTE", "mensagem": "A sequência ou a chave de idempotência já foi usada com outro conteúdo.", "request_id": "50f2a390-4c3c-438e-9491-4a9cb53f7119" } } -
Valide e leia a prévia. É aqui que aparecem NCM inexistente, linha sem NCM e outras recusas.
{"ingestao_id": "5306d957-04d7-44dd-9c63-e4d04d2e19fe","estado": "pronta_para_aplicar","hash_previa": "4bee6b75d2d85b7a1ed8f4dd9657f131c7d9b1dca79c0eac2c3c912091d131ae","totais": { "lotes": 1, "produtos": 3, "incompletos": 0, "atributos": 0 },"guardas": []}{"ingestao_id": "2e61f52d-9802-4a25-bf23-6ccbe9dc39ea","estado": "precisa_revisao","hash_previa": "1e85ffa7b501a0999f7acc29dbcc773d683f7f915e243852440f04571db7b456","totais": { "lotes": 1, "produtos": 3, "incompletos": 3, "atributos": 0 },"guardas": ["linha_sem_ncm", "ncm_inexistente"]}Cada item recusado aparece em
GET /v1/catalogos/ingestoes/{id}com a mensagem, por exemplo"NCM não existe na nomenclatura vigente.". Tentar aplicar nesse estado devolve409 REVISAO_HUMANA_NECESSARIA. -
Pare aqui. Não chame
aplicar. A ingestão fica parada e o catálogo continua exatamente como estava.
4. Webhooks
Seção intitulada “4. Webhooks”Configurar, testar e reenviar webhooks exige plano pago ativo; sem ele, a API responde 409 ASSINATURA_INATIVA.
Teste o receptor sem depender da rede
Seção intitulada “Teste o receptor sem depender da rede”Antes de expor o endpoint, teste a verificação de assinatura com este vetor fixo. O seu código precisa aceitar exatamente estes valores e recusar qualquer byte diferente no corpo.
| Entrada | Valor |
|---|---|
| Segredo | ctj_whsec_exemplo_de_teste_nao_use_em_producao_000000 |
Cotejar-Event-Id |
3f2b8c1e-5d4a-4b7e-9c2f-1a6d8e0b4c71 |
Cotejar-Timestamp |
1792000000 |
Cotejar-Signature |
v1=536f51b3d450c13da9a5c021f67bf7599fd4923d5fb32c910e01b728d7e5340a |
{"id":"3f2b8c1e-5d4a-4b7e-9c2f-1a6d8e0b4c71","versao":1,"empresa":"12345678","ocorridoEm":"2026-10-14T12:26:40.000Z","dados":{"tipo":"webhook.teste","destinoId":"9b1c2d3e-4f50-4a61-8b72-93a4b5c6d7e8"}}O instante do vetor é fixo. No teste, injete o relógio como 1792000030 (30 segundos depois) para passar na janela de cinco minutos. Os receptores de exemplo aceitam esse parâmetro.
Dispare um evento de teste
Seção intitulada “Dispare um evento de teste”Com o receptor publicado, use Enviar evento de teste em Integrações › Webhooks, ou chame a API com o ETag atual do destino:
ETAG=$(curl -sS -D - -o /dev/null -H "Authorization: Bearer $COTEJAR_API_KEY" \ "$COTEJAR_API_BASE_URL/v1/webhook" | awk -F': ' 'tolower($1)=="etag" {print $2}' | tr -d '\r')
curl -sS -X POST "$COTEJAR_API_BASE_URL/v1/webhook/testes" \ -H "Authorization: Bearer $COTEJAR_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "If-Match: $ETAG" \ -H "Content-Type: application/json" \ -d '{}'O 202 só confirma que o teste entrou na fila. A prova de entrega é o histórico: o evento webhook.teste precisa passar de Pendente para Entregue, com o status HTTP que o seu receptor devolveu.
Pela API, acompanhe em GET /v1/webhook/eventos e veja cada tentativa em GET /v1/webhook/eventos/{id}/tentativas. Testes e reenvios somados têm teto de 5 por minuto.
5. CSV de exemplo
Seção intitulada “5. CSV de exemplo”O CSV da análise só deve ser aceito depois de conferir tamanho, SHA-256, cabeçalho, quantidade de achados e a linha final conclusao. Para testar o seu validador sem rodar uma análise, use este arquivo, gerado pelo mesmo formatador da API:
| Cabeçalho | Valor esperado |
|---|---|
| Arquivo | analise-exemplo.csv |
X-Cotejar-Tamanho-Bytes |
982 |
Content-Digest |
sha-256=:Ou+bAOwk3pO1PDkLEAYp4zBdEkqqgMAl/hq3npIjijY=: |
O arquivo tem dois achados do tipo orfao. Um deles tem o código '=000124: o apóstrofo é a proteção contra fórmula em planilha e precisa ser preservado. Veja o validador pronto.
6. Primeira aplicação controlada
Seção intitulada “6. Primeira aplicação controlada”Quando as camadas anteriores passarem, aplique um lote pequeno com operacao: "mesclar". A mescla adiciona ou atualiza os produtos enviados e preserva todos os ausentes. Comece por produtos que já existem no catálogo, com os mesmos dados do ERP: a aplicação exercita o fluxo inteiro sem mudar o conteúdo.
Evite substituicao_catalogo até a integração estar estável: ela retira do catálogo escolhido os vínculos que não vierem nos lotes.
Lista para entrar em produção
Seção intitulada “Lista para entrar em produção”- A chave de produção vem do gerenciador de segredos e nunca aparece em log.
- Toda escrita gera e persiste a
Idempotency-Keyantes do primeiro envio e a reutiliza nas repetições. -
202é acompanhado peloLocationaté um estado final; timeout não vira sucesso. -
412leva a uma nova leitura, nunca a uma repetição cega. -
429e503respeitamRetry-After. - Lotes com recusa param antes de
aplicar, e alguém é avisado. - O receptor de webhook confere a assinatura com o corpo bruto, recusa instantes fora da janela de cinco minutos e deduplica pelo
Cotejar-Event-Id. - O CSV só é importado depois de conferir tamanho, hash, cabeçalho, contagem e conclusão.
- Existe um lembrete para trocar a credencial antes de
expira_em. - A credencial de homologação foi revogada.

