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 -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"
}'| target | Como identificar | Campos corrigíveis |
|---|---|---|
container | container_number | container_number, container_type, size_in_feet, movement_type, package_count, package_type, gross_weight, tare_weight_empty_container, measurement_volume |
cargo | cargo_id | description, 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) |
party | role | name, tax_id, address.street, address.city e os demais de address e contact |
bl | nada, é único | bl_number, issue_date, reference_numbers.*, voyage.*, freight.* e declared_totals.* |
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.
{"corrections": {}} quando a leitura está certa e você só quer tirá-la da fila. É o caso mais comum de quem usa conferência.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
}
]
}| Campo | O que é |
|---|---|
at | Quando aconteceu, em ISO 8601 UTC |
by | Quem 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 |
source | plataforma, ia, regra, portal ou api |
event | O que aconteceu (tabela abaixo) |
changes | Os 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 |
detail | O detalhe do evento, com forma conforme o event. null quando não há |
| event | O que é | detail |
|---|---|---|
received | Arquivo aceito | file_type, pages, size_bytes |
ocr | Páginas digitalizadas transcritas antes da leitura | pages[]: page, result (read, empty, timed_out, failed, no_response), seconds |
extraction | Leitura concluída, ou reaproveitada de arquivo idêntico | reused, seconds |
normalization | Valor lido padronizado, como NCM sem pontuação | em changes |
auto_correction | Reparo com prova, como dígito verificador do contêiner | em changes |
allocation | Vínculo carga × contêiner deduzido | links[]: cargo_id, container_number, basis, evidence |
validation | Checagem, na leitura e depois de cada correção | after, score, findings[]; na leitura, threshold e status |
vessel | O navio no registro da IMO | vessel_name, imo_number, imo_number_valid, result, name_differs, registry[] |
correction | Alteração humana | em changes |
confirmation | Conferido sem alteração | null |
failed | Leitura terminou sem resultado, sem cobrança | error_code |
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_..."
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 achado | Efeito na nota |
|---|---|
high | Zera a nota de uma vez, não importa quantos |
low | Tira 0,10 cada |
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."
}
]
}
}| Campo | O que é |
|---|---|
score | A nota do documento, de 0 a 1 |
threshold | O limiar que valia para a conta nesta leitura. null quando a conta não usa a fila |
reproved | true quando a nota ficou abaixo do limiar |
findings | Tudo 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:
| code | O que testa | Peso inicial |
|---|---|---|
invalid_container_number | O número não passa no dígito verificador da ISO 6346 | Grave |
invalid_cnpj | Os dígitos verificadores do CNPJ não conferem | Leve |
ncm_inexistente | O 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_implausivel | O código não tem um comprimento que a NCM use | Leve |
totals_mismatch_weight | A soma do peso dos itens diverge do total impresso | Leve |
totals_mismatch_packages | A soma dos volumes diverge do total impresso | Leve |
totals_mismatch_measurement | A soma da cubagem diverge do total impresso | Leve |
same_cnpj_different_names | O mesmo CNPJ aparece sob dois nomes de empresa no documento | Ignorada |
document_is_draft | O documento traz marca de rascunho | Ignorada |
invalid_cep | Endereço no Brasil com CEP que não tem 8 dígitos | Ignorada |
onu_formato_invalido | Número ONU declarado sem exatamente quatro dígitos | Ignorada |
classe_risco_invalida | Classe ou risco subsidiário fora das classes da ONU | Ignorada |
grupo_embalagem_invalido | Grupo de embalagem que não é I, II ou III | Ignorada |
ems_formato_invalido | EmS fora do formato do IMDG (F-A a F-J, S-A a S-Z) | Ignorada |
ponto_fulgor_nao_confere_com_grupo | Na classe 3, o ponto de fulgor em copo fechado cai em outro grupo de embalagem | Ignorada |
marca_hm_diverge | Coluna HM marcada sem declaração, ou declaração com a coluna em branco | Ignorada |
onu_inexistente | O número ONU não existe na lista da ONU carregada | Ignorada |
classe_nao_confere_com_onu | A classe declarada não é a que a lista dá para o número | Ignorada |
grupo_nao_permitido_para_onu | A lista não tem o número com o grupo declarado | Ignorada |
nome_diverge_do_onu | O nome declarado não confere com o que a lista dá para o número | Ignorada |
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.
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": { }
}