API REST — contrato de integração¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-10
Referência da API (OpenAPI/Swagger) → · API Reference (Javadoc) →
Audiência: quem integra um sistema cliente (ERP, job agendado, script) com o processador. Não há tela de upload — o envio da planilha é só por esta API.
O contrato do arquivo (layout das 3 abas — colunas, índices, tipos) e do
resultado canônico do processamento está em
schema-processamento-xls.json (JSON Schema).
Como enviar a planilha¶
POST /api/xls/processar — multipart/form-data, autenticado por Bearer.
Campo único obrigatório: arquivo — um .zip contendo 1..N .xlsx do ciclo
(o zip é o lote). O cliente não envia mais data/hora: o período de cada
planilha é lido das próprias abas (decisão
0016, que superou a
0004). O servidor gera o id do
lote (zip- + os 60 primeiros hex do SHA-256 do .zip, idempotente), agrupa as
entradas e manda um resumo único no Telegram
quando todas terminam.
BASE="https://excel.vantroba.xadm.biz"
TOKEN="<mesmo valor de VANTROBA_XLS_API_TOKEN no servidor>"
# Monte o lote: zipe os .xlsx do ciclo num único .zip (o -j descarta caminhos).
zip -j ciclo.zip ./saida/*.xlsx
curl -sS -X POST "$BASE/api/xls/processar" \
-H "Authorization: Bearer $TOKEN" \
-F "arquivo=@./ciclo.zip;type=application/zip"
No Windows (curl.exe, PowerShell/cmd): mesma chamada, trocando a quebra de
linha \ por ^ (cmd) ou ` (PowerShell), e usando aspas duplas no campo
-F. Pra montar o zip: Compress-Archive -Path .\saida\*.xlsx -DestinationPath .\ciclo.zip.
Python (requests) — monta o .zip em memória e envia num passo só:
import glob, io, zipfile, requests
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
for caminho in glob.glob("saida/*.xlsx"):
z.write(caminho, arcname=caminho.split("/")[-1]) # só o nome, sem o path
buf.seek(0)
r = requests.post(
f"{BASE}/api/xls/processar",
headers={"Authorization": f"Bearer {TOKEN}"},
files={"arquivo": ("ciclo.zip", buf, "application/zip")},
timeout=120)
r.raise_for_status(); print(r.json()) # {conjuntoId, total, itens:[...]}
Respostas do POST¶
O 202 traz o recibo do lote — conjuntoId (zip-<60 hex do sha256>), total de
.xlsx e itens[] com o status por arquivo:
| HTTP (lote) | Situação | Corpo |
|---|---|---|
202 |
Lote recebido; processamento agendado por arquivo | conjuntoId, total, itens[] |
400 |
Zip inválido, vazio ou sem .xlsx |
erro RFC 7807 |
401 |
Bearer ausente ou inválido | — |
Cada entrada de itens[] tem nome, id (nulo quando não processado),
httpStatus e mensagem. O httpStatus por arquivo preserva a semântica de
idempotência que antes vinha no POST de um XLS avulso:
httpStatus do item |
Significado |
|---|---|
202 |
Novo processamento enfileirado (assíncrono) |
200 |
Mesmo arquivo já processado com sucesso (dedupe por checksum) |
409 |
Mesmo checksum já em fila / processando |
400 |
Entrada não aceita (não é .xlsx, sem uma das 3 abas 10/20/30, aba 10 sem período em E1/F1) |
Todo erro de nível de lote (400/401, e também falhas de validação e rotas
inexistentes) sai no formato RFC 7807 (application/problem+json):
{"type": "about:blank", "title": "...", "status": 400, "detail": "...",
"code": "BAD_REQUEST"} — status é o código HTTP, title o resumo, detail a
mensagem específica, e code (extensão) é o código estável legível por máquina.
O 2xx mantém o corpo de negócio (conjuntoId/itens[]), nunca o code.
O processamento é assíncrono: para cada id de item, faça polling em
GET …/processamentos/{id} até SUCESSO/ERRO, ou acompanhe pelo resumo do
Telegram. (Falhas de processamento depois do aceite viram status ERRO, não
400 — ver o detalhe e os logs do processamento.)
Leitura (GET, sem Bearer)¶
Os GETs de leitura são anônimos neste projeto:
GET /api/transporte/processamentos?page=0&size=20&status=SUCESSO— lista paginada (JSON)GET /api/transporte/processamentos/{id}— detalhe (JSON)GET /api/transporte/processamentos/{id}/logs— log (text/plain)GET /api/transporte/processamentos/{id}/arquivo— XLS original (em qualquer modo de storage; noPSQLlê dearquivo_bytes)
Interface humana: GET /processamentos (HTML, protegida por login — ver
etapa 07).
Limites e boas práticas¶
- Respeitar
micronaut.server.multipart.max-file-size(50 MB por padrão). - HTTPS sempre em produção.
- Em
409, aguardar o término do processamento em andamento. - Swagger UI interativo:
https://<host>/swagger-ui(botão Authorize para o Bearer).