# Export

POST `/sql/v1/query/export`

Runs the same query as [`/sql/v1/query`](https://dev.nominal.io/sql-reference/rest-api/query.md) without a row cap or a result-size cap, writes the full result to a file in object storage, and returns a presigned download URL.

## Request

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `query` | string | Yes | The `SELECT` statement. Maximum 100,000 characters. |
| `workspace_rid` | string | Yes | Workspace to resolve datasets against. |
| `format` | enum | No | File format to write: `SQL_SERVICE_EXPORT_FORMAT_CSV` or `SQL_SERVICE_EXPORT_FORMAT_PARQUET`. Defaults to CSV. |
| `parquet_options` | object | No | How to write a Parquet file. See [Parquet options](https://dev.nominal.io/sql-reference/rest-api/export.md#parquet-options). Only valid when `format` is `SQL_SERVICE_EXPORT_FORMAT_PARQUET`. |

Export has no row limit and no result-size cap, which is the point of using it instead of query. To bound an export, put a `LIMIT` in the query. Exports have the same two-minute execution budget as the query endpoint, and a query that reads metadata tables is held to the same [metadata limit](https://dev.nominal.io/sql-guides/writing-queries.md#joins).

```shell
curl --request POST '{API_URL}/sql/v1/query/export' \
  --header 'Authorization: Bearer <your-api-key>' \
  --header 'Content-Type: application/json' \
  --data '{
    "query": "SELECT ts, value FROM points_double WHERE dataset_rid = '\''<dataset-rid>'\'' ORDER BY ts",
    "workspace_rid": "<workspace-rid>"
  }'
```

To get a Parquet file, set `format`:

```shell
curl --request POST '{API_URL}/sql/v1/query/export' \
  --header 'Authorization: Bearer <your-api-key>' \
  --header 'Content-Type: application/json' \
  --data '{
    "query": "SELECT ts, value FROM points_double WHERE dataset_rid = '\''<dataset-rid>'\'' ORDER BY ts",
    "workspace_rid": "<workspace-rid>",
    "format": "SQL_SERVICE_EXPORT_FORMAT_PARQUET"
  }'
```

### Parquet options

| Field | Type | Notes |
| --- | --- | --- |
| `row_group_rows` | integer | Rows per row group, from 1,000 to 100,000,000. Omit it to use the service’s default. |
| `compression` | enum | Column compression: `SQL_SERVICE_PARQUET_COMPRESSION_ZSTD` (the default), `SQL_SERVICE_PARQUET_COMPRESSION_LZ4`, `SQL_SERVICE_PARQUET_COMPRESSION_SNAPPY`, or `SQL_SERVICE_PARQUET_COMPRESSION_NONE`. |

## Response

| Field | Type | Notes |
| --- | --- | --- |
| `presignedUrl` | string | Download URL for the exported file. Time-limited, and carries its own credentials as query parameters, so treat it as a secret and quote it in a shell. |
| `queryId` | string | Server-generated id for the execution. |

For example:

```json
{
  "presignedUrl": "https://<bucket>.s3-fips.<region>.amazonaws.com/export-sql/.../<id>.csv?X-Amz-Security-Token=...&X-Amz-Expires=21600&X-Amz-Signature=...",
  "queryId": "bca640f1-4d76-4c48-96b6-011bd747872d"
}
```

When the URL expires, run the export again for a new one.

The query runs to completion before the URL comes back, so a broad query over a large dataset can take a while to return. Give your HTTP client a timeout longer than the two-minute execution budget.

CSV files have a header row of column names, and nulls are empty fields. Parquet files keep the column types.

## Supported queries

Export accepts the same queries as the query endpoint, over telemetry tables, metadata tables, or both. It has to be configured for the deployment, and where it isn’t, calls return `SQL_ERROR_EXPORT_NOT_CONFIGURED`.

## Next steps

- [Query](https://dev.nominal.io/sql-reference/rest-api/query.md): run a bounded query and get the result in the response.
- [Error handling](https://dev.nominal.io/sql-reference/rest-api/errors.md): error codes and response shape.
