Primeira leitura
Do zero ao primeiro JSON de um Bill of Lading.
1. Tenha uma chave
Criada no portal, em Conta › Chaves de API. O passo a passo está em Autenticação, e vale para todos os produtos.
2. Envie o arquivo
O arquivo vai como multipart/form-data, num campo chamado file. É o arquivo cru, do mesmo jeito que um formulário HTML envia.
| Item | Valor |
|---|---|
| Content-Type da requisição | multipart/form-data |
| Nome do campo | file |
| Formatos aceitos | PDF, JPEG, PNG |
| Tamanho | até 5 MB |
| Páginas | até 5 |
Não mande base64, nem JSON com o arquivo dentro, nem um link para baixar. Um corpo
application/json com o PDF em base64 é recusado. Se a sua linguagem chama isso de Blob, File, FormData ou stream, tudo bem: o que importa é a requisição sair como multipart, com os bytes no campo file.curl
curl -X POST https://comexdoc.com.br/v1/ebl/documents \ -H "Authorization: Bearer cxd_live_..." \ -H "Idempotency-Key: 3f7c1e9a-2b44-4d1e-9a10-7c5b2e8d4f11" \ -F "file=@bl.pdf"
Node
import { readFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";
const form = new FormData();
form.append(
"file",
new Blob([await readFile("bl.pdf")], { type: "application/pdf" }),
"bl.pdf",
);
const res = await fetch("https://comexdoc.com.br/v1/ebl/documents", {
method: "POST",
headers: {
Authorization: "Bearer cxd_live_...",
"Idempotency-Key": randomUUID(),
// Não defina Content-Type aqui: o FormData põe o boundary sozinho.
},
body: form,
});Python
import uuid, requests
with open("bl.pdf", "rb") as f:
r = requests.post(
"https://comexdoc.com.br/v1/ebl/documents",
headers={
"Authorization": "Bearer cxd_live_...",
"Idempotency-Key": str(uuid.uuid4()),
},
files={"file": ("bl.pdf", f, "application/pdf")},
)PHP
$ch = curl_init("https://comexdoc.com.br/v1/ebl/documents");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer cxd_live_...",
"Idempotency-Key: " . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => [
"file" => new CURLFile("bl.pdf", "application/pdf", "bl.pdf"),
],
]);
$resposta = curl_exec($ch);Um erro comum em Node e em JavaScript de navegador: definir
Content-Type: multipart/form-data na mão. Isso apaga o boundary que o FormData gera, e o servidor não consegue separar as partes. Deixe esse cabeçalho de fora.Além do arquivo, o corpo aceita um campo opcional webhook_url, para mandar o resultado desta leitura para um endereço diferente do destino cadastrado no portal.
A resposta é 202, na hora. Ela não traz o resultado, traz como buscá-lo:
{
"schema_version": "1.0",
"job_id": "6fbb2659-6e9f-4f00-a211-dd1a7fef2dda",
"status": "queued",
"document_hash": "efca21eda0263a4c47c508719b3151d1787b472e9223e30b057a3ee37affa380",
"status_url": "/v1/ebl/documents/6fbb2659-6e9f-4f00-a211-dd1a7fef2dda",
"created_at": "2026-09-05T23:28:30.214Z"
}Mande sempre o
Idempotency-Key. É um UUID seu, um por arquivo. Se a resposta se perder na rede e você repetir a requisição com a mesma chave, devolvemos o mesmo job_id em vez de ler o documento outra vez. Sem ele, um retry automático do seu cliente HTTP vira uma segunda leitura paga. A chave vale por 24 horas.3. Pegue o resultado
curl https://comexdoc.com.br/v1/ebl/documents/6fbb2659-... \ -H "Authorization: Bearer cxd_live_..."
Enquanto a leitura corre, status é queued ou processing e data vem null. Quando termina, data traz o documento inteiro.
| status | O que significa |
|---|---|
queued | Aceito, esperando a vez na fila |
processing | Sendo lido agora |
completed | Pronto. O JSON está em data |
review_required | Pronto, mas algo pede conferência humana. Não é erro: vem com 200 e o JSON completo |
reviewed | Conferido por uma pessoa no portal |
failed | Não foi possível ler. O motivo está em error_code |
Consultar em laço funciona, mas o webhook poupa requisição. Uma leitura leva cerca de dois minutos; perguntar de dez em dez segundos são doze chamadas para uma resposta. Se você já tem um servidor exposto, veja Receber por webhook.
O arquivo original continua disponível
GET /v1/ebl/documents/{job_id}/file devolve o arquivo exatamente como você enviou. Ele não expira.