---
url: 'https://docs.horusbi.com.br/api/upload.md'
description: >-
  Como enviar pela API um arquivo de até 2 GB com URL pré-assinada, e quando o
  envio multipart de até 200 MiB serve.
---

# Upload grande

A API guarda arquivos do Tenant por dois caminhos. O envio multipart (`POST /tenant/files/upload`) aceita até 200 MiB num pedido só. Acima disso, até 2 GB, o arquivo vai por URL pré-assinada: você pede a URL à API, manda os bytes direto ao armazenamento e confirma. As outras operações de arquivo (listar, tornar público, gerar link, excluir) estão no grupo **Arquivos** do menu.

## Antes de começar

O [Tenant Token](/api/#autenticacao) do Tenant onde o arquivo vai ficar.

## Qual caminho usar

| | Multipart | URL pré-assinada |
|---|---|---|
| Tamanho máximo | 200 MiB | 2 GB |
| Pedidos | 1 | 3 (pedir URL, `PUT`, confirmar) |
| Os bytes passam pela API | Sim | Não |
| `origin` do arquivo | `api` | `hec` |
| Mesmo nome de novo | Substitui o conteúdo, mesmo `id` | Cria outro arquivo |

Acima de 200 MiB, o multipart responde 413 com o texto `File size limit has been reached`, sem JSON.

## Enviar até 200 MiB

```bash
curl -X POST "https://api.horusbi.com.br/v1/tenant/files/upload" \
  -H "Authorization: Bearer $TENANT_TOKEN" \
  -F "file=@vendas-setembro.csv" \
  -F "filename=vendas-setembro.csv"
```

O campo do arquivo precisa se chamar `file`. A resposta traz `id` e `url`. O arquivo entra privado.

## Enviar até 2 GB

1. Peça a URL de envio. O `mime` que você manda aqui é o `Content-Type` que o `PUT` vai ter de usar:

   ```bash
   curl -X POST "https://api.horusbi.com.br/v1/tenant/files/presign" \
     -H "Authorization: Bearer $TENANT_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"file_name": "vendas-2026.parquet", "mime": "application/vnd.apache.parquet"}'
   ```

   A resposta traz `id` e `uploadUrl`. A URL vale 15 minutos.

2. Mande o arquivo com `PUT` na `uploadUrl`, com o mesmo `Content-Type`:

   ```bash
   curl -X PUT "$UPLOAD_URL" \
     -H "Content-Type: application/vnd.apache.parquet" \
     --upload-file vendas-2026.parquet
   ```

   Esse pedido vai direto ao armazenamento e não leva o Tenant Token.

3. Confirme o envio com o `id` do passo 1:

   ```bash
   curl -X POST "https://api.horusbi.com.br/v1/tenant/files/confirm" \
     -H "Authorization: Bearer $TENANT_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "mime": "application/vnd.apache.parquet"}'
   ```

   A API lê o tamanho real no armazenamento e devolve os dados do arquivo, com `file_size` e `url`. O arquivo entra privado.

Até a confirmação, o arquivo não aparece em `GET /tenant/files`. O que não for confirmado em 24 horas é apagado.

O mesmo fluxo em Node.js:

```js
import { readFile } from "node:fs/promises";

const BASE = "https://api.horusbi.com.br/v1/tenant";
const auth = { Authorization: `Bearer ${process.env.TENANT_TOKEN}`, "Content-Type": "application/json" };
const mime = "application/vnd.apache.parquet";

const presign = await fetch(`${BASE}/files/presign`, {
  method: "POST",
  headers: auth,
  body: JSON.stringify({ file_name: "vendas-2026.parquet", mime }),
}).then((r) => r.json());
const { id, uploadUrl } = presign.data;

const put = await fetch(uploadUrl, {
  method: "PUT",
  headers: { "Content-Type": mime },
  body: await readFile("./vendas-2026.parquet"),
});
if (!put.ok) throw new Error(`PUT falhou: ${put.status}`);

const confirm = await fetch(`${BASE}/files/confirm`, {
  method: "POST",
  headers: auth,
  body: JSON.stringify({ id, mime }),
}).then((r) => r.json());
console.log(confirm.data.url);
```

## Abrir o arquivo depois

O arquivo entra privado: a `url` só abre com um link assinado. Gere o link com `POST /tenant/files/{id}/sign`, com a validade em segundos (`ttl`, de 60 a 604800; sem ele, 1 hora). Para a `url` abrir sem token, torne o arquivo público com `PATCH /tenant/files/{id}` e `{"public": true}`.

Excluir (`DELETE /tenant/files/{id}`) tira o arquivo da lista na hora. O conteúdo é apagado do armazenamento 7 dias depois, sem como restaurar pela API.

## Se der errado

| Sintoma | Causa | Conserto |
|---|---|---|
| `413 File size limit has been reached` no multipart | Arquivo acima de 200 MiB | Use a URL pré-assinada |
| `PUT` responde 403 | `Content-Type` diferente do `mime` do passo 1, ou URL vencida | Repita o passo 1 e use o mesmo valor nos dois |
| Confirmação responde `Arquivo não encontrado no storage (upload não concluído)` | O `PUT` não terminou ou falhou | Refaça o `PUT` e confirme de novo |
| Confirmação responde `Arquivo excede o limite de ~2 GB por arquivo` (`FILE_TOO_LARGE`) | Arquivo acima de 2 GB | Divida o arquivo |

## Relacionado

* [API](/api/): tokens, erros e limite de pedidos.
* [HorusETL](/etl/): ler um arquivo guardado num Dataflow.
