Buscar K
Aparência
Aparência
Esta página documenta a API REST de gestão de arquivos da plataforma HorusBI, disponível para integrações server-to-server (headless). Com ela, você pode fazer upload, listar, consultar metadados, controlar visibilidade, gerar links temporários e excluir arquivos do repositório de um tenant.
Todos os endpoints de /v1/tenant/files* retornam respostas no envelope padrão da plataforma:
Sucesso:
{ "status": "success", "data": <payload> }Erro:
{ "status": "error", "message": "Descrição do erro" }Os exemplos de resposta ao longo desta página já mostram o envelope completo.
Todos os endpoints ficam sob /v1/tenant e usam o Tenant Bearer Token (sys_tenant_api_token):
Authorization: Bearer <sys_tenant_api_token>O token identifica o tenant. Sem ele, todas as rotas retornam 401 Unauthorized.
Escopo: os endpoints REST de Arquivos operam no tenant do token bearer e não são filtrados por usuário (sem owner-scoping). O controle por função (
files) e por kind (ETL viaflow) aplica-se às telas do HEC/ETL, não à API headless.
Cada arquivo tem uma origin que indica como foi criado:
| Origin | Quem cria | Padrão de visibilidade |
|---|---|---|
api | REST API (este endpoint) | Privado |
hec | Upload manual via HEC, ou via presign+confirm na REST | Privado |
dw | Sistema (writeback de Cadastros, exportações) | Público |
etl | Pipeline ETL | Privado |
Para arquivos grandes (ex.: parquets de 700 MB), use o fluxo presigned que evita passar bytes pelo backend:
1. POST /v1/tenant/files/presign → { id, key, uploadUrl }
2. PUT <uploadUrl> → (direto no S3, Content-Type no header)
3. POST /v1/tenant/files/confirm → FileDTO completoPara arquivos pequenos (até ~1 GB), /v1/tenant/files/upload com multipart/form-data também funciona.
Limite por arquivo: o backend lê o tamanho real via
HeadObjectno S3 ao confirmar o upload (campofile_sizenão é aceito no body do/files/confirm). O teto é 2 147 483 647 bytes (~2 GB, limite da colunaint4). O/files/upload(multipart) é limitado a ~1 GB pelo middleware de streaming.
Content-Type no PUT do presign (footgun): ao fazer o
PUTdiretamente nauploadUrlretornada pelo/files/presign, o cliente deve enviar o headerContent-Typecom o valor exatamente igual aomimeinformado na chamada ao/files/presign. O S3 inclui oContent-Typena assinatura da URL; qualquer divergência resulta emSignatureDoesNotMatch(HTTP 403).
Todos os endpoints de leitura retornam objetos no formato FileDTO:
| Campo | Tipo | Descrição |
|---|---|---|
id | string (UUID) | Identificador único do arquivo |
file_name | string | Nome do arquivo |
file_size | integer | null | Tamanho em bytes (null enquanto o presign não é confirmado) |
mime | string | null | MIME type (ex.: application/parquet, image/png) |
origin | string | Origem: api, hec, dw ou etl |
public | boolean | true = URL pública sem autenticação; false = requer token assinado |
criado_por | integer | null | ID do usuário que fez o upload; null quando não há contexto de usuário |
criado_em | string (ISO 8601) | Data/hora de criação |
url | string | URL canônica do arquivo: https://storage.horusbi.com.br/f/{id}/{file_name} |
uploader | string | Rótulo legível de quem enviou: nome do usuário (quando criado_por != null), ou "API" / "ETL" / "Sistema" (quando criado_por é null, derivado da origin) |
uploader— lógica de resolução: secriado_porestá preenchido, é onomedo usuário emsys_user; se não está, o rótulo vem daorigin:api→"API",etl→"ETL",dwouhec→"Sistema".
GET /v1/tenant/files Lista os arquivos do tenant, paginado, ordenado por data de criação decrescente. Exclui arquivos marcados como excluídos (excluido=true).
Nota: uploads presigned abandonados (reservas criadas pelo
/presignmas nunca confirmadas) não aparecem nesta listagem. Apenas arquivos com upload concluído são retornados.
Query params:
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
limit | integer | 50 | Máximo de registros por página (teto: 200) |
offset | integer | 0 | Número de registros a pular (paginação) |
origin | string | — | Filtro de origem: api, hec, dw ou etl |
Resposta (200):
{
"status": "success",
"data": {
"rows": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"file_name": "relatorio-junho.parquet",
"file_size": 734003200,
"mime": "application/octet-stream",
"origin": "api",
"public": false,
"criado_por": null,
"criado_em": "2026-06-26T14:30:00.000Z",
"url": "https://storage.horusbi.com.br/f/a1b2c3d4-e5f6-7890-abcd-ef1234567890/relatorio-junho.parquet",
"uploader": "API"
}
],
"total": 42
}
}GET /v1/tenant/files/:id Retorna os metadados de um único arquivo. Retorna 404 se o arquivo não existir, pertencer a outro tenant, ou estiver excluído.
Path params: :id — UUID do arquivo
Resposta (200):
{ "status": "success", "data": { /* FileDTO — ver shape acima */ } }Resposta (404):
{ "status": "error", "message": "Arquivo não encontrado" }POST /v1/tenant/files/upload Upload multipart de um arquivo (caminho compatível, adequado para arquivos até ~1 GB). O arquivo é transmitido em streaming direto para o S3 — sem bufferizar em memória.
Tipo de conteúdo: multipart/form-data
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
file | file | sim | O arquivo a fazer upload (nome do campo deve ser "file") |
filename | string | não | Nome desejado para o arquivo; se omitido, usa o nome original do upload |
O limite de tamanho é ~1 GB. Para arquivos maiores, use o fluxo presign → PUT → confirm.
Resposta (200):
{
"status": "success",
"data": {
"url": "https://storage.horusbi.com.br/f/a1b2c3d4-e5f6-7890-abcd-ef1234567890/relatorio-junho.parquet",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}Nota: esta rota cria o arquivo com
origin: "api"ecriado_por: null. Arquivos com o mesmofile_nameno tenant são reutilizados (idempotente por nome): se já existe um arquivo ativo com esse nome, o upload substitui o conteúdo no S3 e atualiza os metadados.
Respostas de erro:
| Código | Situação |
|---|---|
400 | Nenhum arquivo enviado, ou campo não se chama "file" |
500 | Erro interno (ex.: falha no S3) |
POST /v1/tenant/files/presign Passo 1 do fluxo de upload grande. Cria a linha no banco (origin=hec, public=false) e retorna uma URL pré-assinada para PUT direto no S3. A URL expira em 15 minutos (900 segundos).
Body (JSON):
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
file_name | string | sim | Nome do arquivo a criar |
mime | string | não | MIME type (ex.: application/parquet); padrão: application/octet-stream |
Resposta (200):
{
"status": "success",
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"key": "42/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"uploadUrl": "https://horusbi-uploads.s3.amazonaws.com/42/a1b2c3d4-...?X-Amz-Signature=..."
}
}Respostas de erro:
| Código | Situação |
|---|---|
400 | file_name ausente |
POST /v1/tenant/files/confirm Passo 3 do fluxo de upload grande. Após o PUT direto no S3, confirme o upload para preencher file_size, mime e file_path na linha criada pelo /presign. Retorna o FileDTO completo.
Tamanho lido do S3: o backend consulta o S3 via
HeadObjectpara obter o tamanho real do objeto — o campofile_sizenão é aceito no body. O confirm falha se oPUTnão tiver sido concluído (objeto ausente no S3). O teto de ~2 GB é aplicado contra o tamanho real lido.
Body (JSON):
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | sim | UUID retornado pelo /presign |
mime | string | não | MIME type efetivo do arquivo |
Resposta (200):
{ "status": "success", "data": { /* FileDTO completo — ver shape acima */ } }Respostas de erro:
| Código | Situação |
|---|---|
400 | id ausente, ou arquivo não encontrado / já excluído |
400 | "Arquivo não encontrado no storage (upload não concluído)" — PUT nunca foi feito ou falhou |
400 | "Arquivo excede o limite de ~2 GB por arquivo" — tamanho real lido do S3 supera o teto int4 |
PATCH /v1/tenant/files/:id Alterna a visibilidade pública/privada do arquivo.
Path params: :id — UUID do arquivo
Body (JSON):
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
public | boolean | sim | true = público (URL sem autenticação); false = privado (requer token assinado) |
Resposta (200):
{ "status": "success", "data": { "ok": true } }Respostas de erro:
| Código | Situação |
|---|---|
400 | Arquivo não encontrado |
POST /v1/tenant/files/:id/sign Gera um link temporário assinado para um arquivo privado. O token é armazenado no Redis com TTL; a URL resultante pode ser compartilhada com qualquer pessoa durante a validade.
Path params: :id — UUID do arquivo
Body (JSON):
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
ttl | integer | não | Validade em segundos. Intervalo aceito: 60 – 604800 (1 min a 7 dias). Padrão: 3600 (1h). Valores fora do intervalo são clamped para o mínimo ou máximo; ausente/inválido → padrão. |
Resposta (200):
{
"status": "success",
"data": {
"token": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"url": "https://storage.horusbi.com.br/f/a1b2c3d4-e5f6-7890-abcd-ef1234567890/relatorio.parquet?token=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
}
}Acesso por visibilidade:
- Público (
public: true): a URL canônicahttps://storage.horusbi.com.br/f/{id}/{file_name}funciona sem autenticação. O/signainda funciona para gerar tokens, mas não é necessário.- Privado (
public: false): a URL sem?token=retorna erro. Use/signpara gerar um token temporário e acessar com?token=<token>. O token é reutilizável até expirar.
Respostas de erro:
| Código | Situação |
|---|---|
400 | Arquivo não encontrado |
DELETE /v1/tenant/files/:id Soft-delete do arquivo: marca como excluído (excluido=true) sem remover do S3 imediatamente. O arquivo deixa de aparecer nas listagens e não é mais acessível. O storage para de ser contado no billing a partir da exclusão.
Restrição: arquivos de origem dw que sejam imagens de Cadastro (writeback) e que ainda estejam referenciados em registros ativos ou histórico são bloqueados. O sistema verifica a URL pública antes de excluir.
Path params: :id — UUID do arquivo
Resposta (200):
{ "status": "success", "data": { "ok": true } }Respostas de erro:
| Código | Mensagem | Situação |
|---|---|---|
400 | "Arquivo não encontrado" | ID inexistente, de outro tenant, ou já excluído |
400 | "Arquivo ainda referenciado por um Cadastro (writeback)" | Imagem de writeback (origin: "dw") com referências ativas em wb_record ou wb_record_history |
A exclusão via DELETE /files/:id é um soft-delete: o arquivo é marcado como excluído e some imediatamente das listagens e do billing (não entra mais nos snapshots diários de storage). O conteúdo físico no S3 é removido posteriomente pelo reaper diário, que varre os arquivos soft-deletados e os purga do storage — reclamando os bytes e encerrando qualquer cobrança residual.
Chamadas a POST /files/presign que nunca forem seguidas de um PUT + POST /files/confirm criam entradas "fantasma" no banco (sem file_path). Essas entradas não aparecem nas listagens e são limpas automaticamente pelo mesmo processo de reaper, sem nenhuma ação do integrador.
Exemplo em JavaScript para upload de um arquivo grande sem passar pelo backend:
const BASE = "https://api.horusbi.com.br/v1/tenant";
const headers = {
Authorization: `Bearer ${TENANT_TOKEN}`,
"Content-Type": "application/json",
};
// 1. Solicitar URL pré-assinada
const presignRes = await fetch(`${BASE}/files/presign`, {
method: "POST",
headers,
body: JSON.stringify({
file_name: "dados-junho.parquet",
mime: "application/octet-stream",
}),
});
const { data: { id, uploadUrl } } = await presignRes.json();
// 2. PUT direto no S3 (sem passar pelo backend)
const fileBuffer = await fs.promises.readFile("./dados-junho.parquet");
await fetch(uploadUrl, {
method: "PUT",
headers: { "Content-Type": "application/octet-stream" },
body: fileBuffer,
});
// 3. Confirmar o upload — file_size NÃO é enviado; o backend lê o tamanho do S3
const confirmRes = await fetch(`${BASE}/files/confirm`, {
method: "POST",
headers,
body: JSON.stringify({
id,
mime: "application/octet-stream",
}),
});
const { data: fileDTO } = await confirmRes.json();
console.log("Arquivo criado:", fileDTO.url);| Código | Situação |
|---|---|
400 | Validação do body falhou, arquivo não encontrado, ou guard de writeback ativo |
401 | Token ausente ou inválido |
404 | Arquivo não encontrado (em GET /files/:id) |
500 | Erro interno (ex.: falha no S3 ou banco) |