> ## Documentation Index
> Fetch the complete documentation index at: https://firebolt-aggregate-helm-docs-pr-79.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Send local files with a SQL statement in one HTTP request and query them with the upload:// URL scheme.

# Upload and query local files

The Firebolt HTTP API accepts `multipart/form-data` POST requests that carry a SQL statement together with one or more files (Parquet, CSV, ...).
The statement reads each file with the `upload://` URL scheme through table-valued functions such as [READ\_PARQUET](/reference-sql/functions-reference/table-valued/read_parquet), [READ\_CSV](/reference-sql/functions-reference/table-valued/read_csv), [READ\_AVRO](/reference-sql/functions-reference/table-valued/read_avro), [READ\_TEXT](/reference-sql/functions-reference/table-valued/read_text), and [READ\_FILES](/reference-sql/functions-reference/table-valued/read_files).
This lets you query a file on your machine, or load it into a table, without staging it in Amazon S3 first.

Uploaded files exist on the engine's local disk only while the request runs and are deleted when it finishes.
Nothing is persisted unless the statement writes the data, for example with `INSERT INTO ... SELECT`.

## Request format

Send a POST request with a `Content-Type` of `multipart/form-data` to an engine, authenticated like any other [API request](/guides/developing-with-firebolt/using-the-api).
The request must contain:

* Exactly one part with the `name` directive set to `sql`, holding the SQL statement in UTF-8.
* Any number of file parts. Each file part needs a `filename` directive (the value is ignored) and a `name` directive that matches the regex `[_0-9a-zA-Z.-]+` and is unique within the request. The SQL statement references the part as `upload://<name>`.

With curl, `--form "sql=<statement>"` produces the statement part and `--form "<name>=@<path>"` produces a file part.

The engine stores each uploaded file under a unique name built from the part name: a sanitized form of it, a UUID, and the part name's file extension (`events.json.gz` becomes `events_3f9e....json.gz`), which is the name error messages quote.
Format inference reads that extension, so a part name that carries one can be read with [READ\_FILES](/reference-sql/functions-reference/table-valued/read_files), which infers both the format and the compression from it.
The format-specific TVFs take the format from the function you call, so they read a part with any name.

## Query an uploaded file

```bash theme={"theme":{"light":"css-variables","dark":"css-variables"}}
curl "https://<user engine URL>?database=<database>" \
  --header 'Authorization: Bearer <access token>' \
  --form "sql=SELECT * FROM READ_PARQUET('upload://my_file')" \
  --form "my_file=@./sales.parquet"
```

The statement references the part name (`my_file`), not the local file name (`sales.parquet`).
The `upload://` URL must match a part name exactly; glob patterns are not supported.

Naming the part after the file lets `READ_FILES` infer the format, so the statement does not have to name it:

```bash theme={"theme":{"light":"css-variables","dark":"css-variables"}}
curl "https://<user engine URL>?database=<database>" \
  --header 'Authorization: Bearer <access token>' \
  --form "sql=SELECT * FROM READ_FILES('upload://sales.parquet')" \
  --form "sales.parquet=@./sales.parquet"
```

## Upload multiple files

A statement can reference any number of uploaded files, each by its own part name:

```bash theme={"theme":{"light":"css-variables","dark":"css-variables"}}
curl "https://<user engine URL>?database=<database>" \
  --header 'Authorization: Bearer <access token>' \
  --form "sql=SELECT * FROM READ_PARQUET('upload://my_sales') CROSS JOIN READ_CSV('upload://my_targets', HEADER => TRUE, INFER_SCHEMA => TRUE)" \
  --form "my_sales=@./sales.parquet" \
  --form "my_targets=@./targets.csv"
```

## Load an uploaded file into a table

The `upload://` scheme works only in read table-valued functions.
[COPY FROM](/reference-sql/commands/data-management/copy-from) and [external tables](/reference-sql/commands/data-definition/create-external-table) reject it.
To persist uploaded data, insert the function's result into a table:

```bash theme={"theme":{"light":"css-variables","dark":"css-variables"}}
curl "https://<user engine URL>?database=<database>" \
  --header 'Authorization: Bearer <access token>' \
  --form "sql=INSERT INTO sales SELECT * FROM READ_PARQUET('upload://my_sales')" \
  --form "my_sales=@./sales.parquet"
```

## Compress parts

To reduce transfer size, compress individual parts and declare the codec with a per-part `Content-Encoding` header.
The engine decompresses each part on arrival.
Supported encodings are `gzip`, `deflate`, `br`, `xz`, `zstd`, `lz4`, and `snappy`.
Both file parts and the `sql` part can be compressed.

```bash theme={"theme":{"light":"css-variables","dark":"css-variables"}}
gzip targets.csv
curl "https://<user engine URL>?database=<database>" \
  --header 'Authorization: Bearer <access token>' \
  --form "sql=SELECT * FROM READ_CSV('upload://targets', HEADER => TRUE, INFER_SCHEMA => TRUE)" \
  --form "targets=@./targets.csv.gz;headers=Content-Encoding: gzip"
```

A top-level `Content-Encoding` header on the request is rejected; compress per part instead.

The engine stores the decompressed bytes, so a part that declares a `Content-Encoding` must not carry a compression suffix in its part name: the extension describes the file on the engine's disk, and a `.gz` suffix makes compression inference expect gzip-compressed content.
To have the engine read the file compressed instead, upload it without a `Content-Encoding` header and keep the suffix in the part name (`--form "targets.csv.gz=@./targets.csv.gz"`).

## Limits

* The file parts of one request can hold at most 1 GB of data in total, measured after decompression.
