Documentação da API

Conferência e correções

Corrigir o que a leitura errou, e saber depois quem corrigiu.

Para que serve

Toda leitura pode ser corrigida, tenha ela pedido conferência ou não. A correção vale só para a sua conta: o mesmo BL lido por outra empresa não recebe o que você corrigiu.

Depois de uma correção, o GET da leitura passa a devolver o valor corrigido, e não mais o que o modelo tinha lido. Sua integração não precisa juntar as duas coisas.

Corrigir campos

PATCH /v1/ebl/documents/{job_id}. O corpo traz o que corrigir, endereçado pelo que o documento tem de próprio: o número do contêiner, o cargo_id da carga, o papel da parte.

curl
curl -X PATCH https://comexdoc.com.br/v1/ebl/documents/6fbb2659-... \
  -H "Authorization: Bearer cxd_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "corrections": [
      { "target": "container", "container_number": "FCIU6596670",
        "field": "container_number", "value": "FCIU6596671" },
      { "target": "cargo", "cargo_id": 1,
        "field": "description", "value": "EVOTHERM P25" },
      { "target": "party", "role": "consignee",
        "field": "name", "value": "ACME COMERCIO LTDA" },
      { "target": "bl",
        "field": "voyage.vessel_name", "value": "MAERSK MONTE ALEGRE" }
    ],
    "reviewed_by": "operador@terminal.com.br"
  }'
targetComo identificarCampos corrigíveis
containercontainer_numbercontainer_number, container_type, size_in_feet, movement_type, package_count, package_type, gross_weight, tare_weight_empty_container, measurement_volume
cargocargo_iddescription, package_type, package_count, gross_weight, measurement_volume, marks_and_numbers, probable_cargo_owner, country_of_origin, wooden_packaging_statement e ncm_codes.N (o N-ésimo NCM da carga, a partir de 0)
partyrolename, tax_id, address.street, address.city e os demais de address e contact
blnada, é únicobl_number, issue_date, reference_numbers.*, voyage.*, freight.* e declared_totals.*
Por que não pelo caminho do JSON. A mesma carga aparece dentro de cada contêiner que a leva, então containers.0.cargo.0 e containers.1.cargo.0 são a MESMA carga: corrigir uma das duas não quer dizer nada. O cargo_id identifica sem ambiguidade, e você não conta posição em lista nenhuma.

Para uma parte que aparece duas vezes, como um BL com dois notify, mande occurrence com a posição dela dentro do papel, começando em zero. Sem isso a correção do segundo cairia no primeiro.

O valor é texto, número, booleano ou null. Use null para dizer que o campo não consta no documento. Campo que é uma lista inteira, como os lacres de um contêiner, não é corrigível por aqui. A exceção é o NCM, código a código: ncm_codes.0 é o primeiro NCM da carga, ncm_codes.1 o segundo, na ordem em que vêm em ncm_codes. O valor fica só com os dígitos (90.30 vira 9030) e precisa ter de 2 a 8 deles.

A resposta é a leitura completa, já com as correções aplicadas, e o status vira reviewed. Conferência não é cobrada.

Confirmar também é conferir. Mande {"corrections": {}} quando a leitura está certa e você só quer tirá-la da fila. É o caso mais comum de quem usa conferência.
Não existe desfazer. Errou na correção, corrija por cima: vale sempre o último valor, e as duas passagens ficam no histórico. Por isso não há rota de rollback e não há dúvida sobre qual valor está valendo.

reviewed_by é opcional e serve para dizer quem respondeu pela correção quando ela vem de um sistema seu. É texto livre, e fica gravado no histórico.

Quem pode corrigir

Qualquer usuário da sua conta, e qualquer chave de API dela. Não existe perfil de conferente: quem tem acesso ao documento tem acesso à correção. O que segura isso não é permissão, é rastro, e o rastro é o histórico.

Uma leitura de outra conta responde 404, e não 403.

Histórico

GET /v1/ebl/documents/{job_id}/history devolve a linha do tempo do documento, do mais antigo para o mais novo. Somente leitura, e não é cobrado.

É o log inteiro do documento: o arquivo recebido, o OCR de cada página digitalizada, a leitura, o que a plataforma padronizou ou consertou sozinha e por quê, a checagem com os achados, o navio no registro da IMO, cada correção e a reavaliação depois dela.

{
  "schema_version": "1.1",
  "job_id": "6fbb2659-6e9f-4f00-a211-dd1a7fef2dda",
  "history": [
    {
      "at": "2026-09-04T13:22:41Z",
      "by": "Comexdoc",
      "source": "ia",
      "event": "extraction",
      "changes": null,
      "detail": { "reused": false, "seconds": 70 }
    },
    {
      "at": "2026-09-04T13:22:41Z",
      "by": "padronização",
      "source": "regra",
      "event": "normalization",
      "changes": [
        {
          "field": "cargo_items.0.ncm_codes.0.normalized",
          "from": "8414.90.39",
          "to": "84149039",
          "reason": "só dígitos, sem pontuação"
        }
      ],
      "detail": null
    },
    {
      "at": "2026-09-04T15:10:02Z",
      "by": "operador@terminal.com.br",
      "source": "portal",
      "event": "correction",
      "changes": [
        {
          "field": "containers.0.container_number",
          "from": "TCLU7654321",
          "to": "TCLU7654322",
          "reason": null
        }
      ],
      "detail": null
    }
  ]
}
CampoO que é
atQuando aconteceu, em ISO 8601 UTC
byQuem fez: Comexdoc no processamento; a regra no que foi automático; o e-mail de quem agiu no portal; o reviewed_by que você mandou, na API
sourceplataforma, ia, regra, portal ou api
eventO que aconteceu (tabela abaixo)
changesOs campos alterados, com from, to e reason (por que a plataforma mudou; null na correção humana). null nos eventos que não alteram campo
detailO detalhe do evento, com forma conforme o event. null quando não há
eventO que édetail
receivedArquivo aceitofile_type, pages, size_bytes
ocrPáginas digitalizadas transcritas antes da leiturapages[]: page, result (read, empty, timed_out, failed, no_response), seconds
extractionLeitura concluída, ou reaproveitada de arquivo idênticoreused, seconds
normalizationValor lido padronizado, como NCM sem pontuaçãoem changes
auto_correctionReparo com prova, como dígito verificador do contêinerem changes
allocationVínculo carga × contêiner deduzidolinks[]: cargo_id, container_number, basis, evidence
validationChecagem, na leitura e depois de cada correçãoafter, score, findings[]; na leitura, threshold e status
vesselO navio no registro da IMOvessel_name, imo_number, imo_number_valid, result, name_differs, registry[]
correctionAlteração humanaem changes
confirmationConferido sem alteraçãonull
failedLeitura terminou sem resultado, sem cobrançaerror_code
Tipos de evento novos podem aparecer. Ignore os que você não conhece, em vez de rejeitar a resposta. Correção e confirmação valem para o arquivo: numa releitura do mesmo arquivo, as de antes aparecem também, antes do received.

Como toda alteração traz from e to, dá para reconstruir qualquer estado anterior do documento a partir daqui.

A fila de conferência

Se a sua conta usa a fila, uma leitura de confiança baixa termina em review_required em vez de completed. Isso não é erro: vem com 200, o JSON completo e todos os campos preenchidos.

curl "https://comexdoc.com.br/v1/ebl/documents?status=review_required" \
  -H "Authorization: Bearer cxd_live_..."
A fila é opcional e vem desligada. Sem ela, toda leitura válida termina em completed, e você continua podendo corrigir qualquer leitura quando quiser. Quem quer a fila liga no portal, em eBL › Conferência.

Pendência não expira e não é descartada sozinha, então a fila só encolhe quando alguém confere. É por isso que o filtro acima existe.

Por que uma leitura foi para a fila

Quem decide não é o modelo dizendo se está seguro. É um conferidor automático que roda checagens objetivas sobre o que foi lido: dígito verificador de CNPJ e de contêiner, soma dos itens contra o total impresso do documento, NCM contra a tabela vigente da Receita.

O documento começa com nota 1,00 e vai perdendo:

Peso do achadoEfeito na nota
highZera a nota de uma vez, não importa quantos
lowTira 0,10 cada
O peso é da sua conta, não nosso. Cada regra tem um peso inicial, e o administrador da conta muda qualquer um deles no portal, em eBL › Conferência › Configurar. Uma regra pode ser grave para uma conta e ignorada para outra, então o severity que você recebe é o que vale para você.

Regra ajustada para ignorar não roda: não aparece em findings e não conta na nota. Ajuste vale para as leituras seguintes, e não muda nota nem motivos já gravados.

Depois é uma comparação só: nota menor que o limiar da conta vai parareview_required. Nota igual ao limiar passa.

Tudo isso volta no GET da leitura, no campo review, então você não precisa adivinhar o motivo:

{
  "status": "review_required",
  "review": {
    "score": 0,
    "threshold": 0.9,
    "reproved": true,
    "findings": [
      {
        "severity": "high",
        "code": "ncm_inexistente",
        "detail": "Item 1: NCM "8534" não existe na tabela vigente da Receita."
      }
    ]
  }
}
CampoO que é
scoreA nota do documento, de 0 a 1
thresholdO limiar que valia para a conta nesta leitura. null quando a conta não usa a fila
reprovedtrue quando a nota ficou abaixo do limiar
findingsTudo que foi encontrado. Lista vazia quer dizer que nada falhou, não que a checagem deixou de rodar

Ramifique pelo code, que é estável. O detail é texto para pessoa ler, com os valores do documento dentro, e pode mudar de redação.

As checagens que existem hoje, e o peso com que cada conta começa:

codeO que testaPeso inicial
invalid_container_numberO número não passa no dígito verificador da ISO 6346Grave
invalid_cnpjOs dígitos verificadores do CNPJ não conferemLeve
ncm_inexistenteO código não existe na tabela vigente da Receita, nem como classe que ela subdivide, ou nem é um código (lido com letra, como B466)Leve
ncm_tamanho_implausivelO código não tem um comprimento que a NCM useLeve
totals_mismatch_weightA soma do peso dos itens diverge do total impressoLeve
totals_mismatch_packagesA soma dos volumes diverge do total impressoLeve
totals_mismatch_measurementA soma da cubagem diverge do total impressoLeve
same_cnpj_different_namesO mesmo CNPJ aparece sob dois nomes de empresa no documentoIgnorada
document_is_draftO documento traz marca de rascunhoIgnorada
invalid_cepEndereço no Brasil com CEP que não tem 8 dígitosIgnorada
onu_formato_invalidoNúmero ONU declarado sem exatamente quatro dígitosIgnorada
classe_risco_invalidaClasse ou risco subsidiário fora das classes da ONUIgnorada
grupo_embalagem_invalidoGrupo de embalagem que não é I, II ou IIIIgnorada
ems_formato_invalidoEmS fora do formato do IMDG (F-A a F-J, S-A a S-Z)Ignorada
ponto_fulgor_nao_confere_com_grupoNa classe 3, o ponto de fulgor em copo fechado cai em outro grupo de embalagemIgnorada
marca_hm_divergeColuna HM marcada sem declaração, ou declaração com a coluna em brancoIgnorada
onu_inexistenteO número ONU não existe na lista da ONU carregadaIgnorada
classe_nao_confere_com_onuA classe declarada não é a que a lista dá para o númeroIgnorada
grupo_nao_permitido_para_onuA lista não tem o número com o grupo declaradoIgnorada
nome_diverge_do_onuO nome declarado não confere com o que a lista dá para o númeroIgnorada

As checagens de carga perigosa nascem todas ignoradas. A plataforma lê a declaração e entrega o dado; quem valida a carga é o terminal. Ligue as que quiser usar como aviso de leitura: um dígito trocado no número ONU costuma cair em outro produto válido, e nome_diverge_do_onu é a que pega esse caso. As quatro últimas conferem contra a lista de produtos perigosos da ONU e dizem, no detail, contra qual edição conferiram.

Contêiner nasce grave porque o dígito verificador falha quase sempre por um caractere trocado, e contêiner errado só aparece quando a carga não é encontrada. Draft e CNPJ com duas razões sociais nascem ignorados porque são rotina no comércio exterior: draft circula antes do original, e agente e armador dividem inscrição. Ligar as duas por padrão encheria a conferência de ruído que quem é do setor já ignora.

Um código de 4 dígitos como 8534 é uma posição, e passa: a tabela nem sempre traz linha de 4 dígitos, mas traz o que está abaixo dela. Se a tabela subdivide o código, ele existe.
findings vem preenchido mesmo quando a leitura passou. Achado que não foi suficiente para reprovar continua sendo informação: um CEP com menos de 8 dígitos não segura o documento, mas você pode querer tratar antes de gravar no seu sistema.

Uma leitura que falhou não tem nota: sem extração não há o que checar.

A conferência avisa por webhook

Se a sua conta tem destino de entrega configurado, conferir dispara uma entrega nova, com comexdoc-event: ebl.document.reviewed. O corpo é o mesmo do GET: documento com as correções aplicadas e corrected_fields preenchido.

O mesmo job_id chega, então, duas vezes: uma quando a máquina termina, com ebl.document.completed, outra quando alguém confere. O segundo corpo substitui o primeiro. Detalhes em Receber por webhook.

Confirmar sem corrigir nada também avisa: o estado mudou, e uma pessoa aprovou.

Como saber o que veio de gente

O GET da leitura traz corrected_fields, com os caminhos que uma pessoa alterou. Lista vazia quer dizer que tudo ali é o que o modelo leu.

{
  "job_id": "6fbb2659-6e9f-4f00-a211-dd1a7fef2dda",
  "status": "reviewed",
  "corrected_fields": ["containers.0.container_number", "parties.0.name"],
  "data": { }
}