Pular para conteúdo

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; no PSQL lê de arquivo_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).