# Run a SQL query from the v2 Reports API

Execute ad-hoc SQL queries against your Stripe data programmatically.

You can programmatically execute SQL queries against your Stripe data using the `QueryRun` endpoints of the v2 Reports API. This allows you to run the same queries available in the [Sigma editor](https://docs.stripe.com/stripe-data/write-queries.md) without using the Dashboard, enabling you to automate data extraction, schedule queries at custom intervals, and integrate Stripe data into your own systems.

> #### Using the Query Run API
> 
> The Query Run API requires an active [Sigma](https://stripe.com/sigma) subscription.

## API key permissions

To create query runs, your API key must have both the `reporting_write` and `sigma_api_write` permissions. If your key only has `reporting_read`, you can retrieve existing query runs but can’t create new ones. Use the [API keys](https://dashboard.stripe.com/apikeys) page to view and manage permissions. When creating a restricted key in the Dashboard, `reporting_write` appears as **Sigma** and `sigma_api_write` appears as **Sigma API**.

## Create a QueryRun

[Create a QueryRun](https://docs.stripe.com/api/v2/data/reporting/query-runs/create.md?api-version=2026-04-22.preview) by providing a SQL statement in the `sql` parameter. Use the same SQL syntax supported by the [Sigma query editor](https://docs.stripe.com/stripe-data/write-queries.md). The response always includes a new [QueryRun object](https://docs.stripe.com/api/v2/data/reporting/query-runs/object.md?api-version=2026-04-22.preview) with `status=running` and `result=null`.

```curl
curl -X POST https://api.stripe.com/v2/data/reporting/query_runs \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-04-22.preview" \
  --json '{
    "sql": "SELECT * FROM balance_transactions LIMIT 10"
  }'
```

```json
{
  "id": "qryrun_123",
  "object": "v2.data.reporting.query_run",
  "created": "2025-07-03T01:02:29.964Z",
  "sql": "SELECT * FROM balance_transactions LIMIT 10",
  "status": "running",
  "result": null,
  "result_options": {
    "compress_file": false,
    "result_type": "file"
  },
  "livemode": false
}
```

Use the returned `id` to track the progress of the query run.

## Retrieve a QueryRun

[Retrieve a QueryRun](https://docs.stripe.com/api/v2/data/reporting/query-runs/retrieve.md?api-version=2026-04-22.preview) to check its status. When the query completes, access the results with the URL in `result.file.download_url.url`.

```curl
curl https://api.stripe.com/v2/data/reporting/query_runs/qryrun_123 \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-04-22.preview"
```

```json
{
  "id": "qryrun_123",
  "object": "v2.data.reporting.query_run",
  "created": "2025-07-03T01:02:29.964Z",
  "sql": "SELECT * FROM balance_transactions LIMIT 10",
  "status": "succeeded",
  "result": {
    "file": {
      "content_type": "csv",
      "download_url": {
        "expires_at": "2025-07-03T01:10:46.679Z","url": "https://stripeusercontent.com/files/us-west-2/download/wksp_123/file_123/qryrun_123.csv..."
      },
      "size": "512"
    },
    "type": "file"
  },
  "result_options": {
    "compress_file": false,
    "result_type": "file"
  },
  "livemode": false
}
```

> The download URL is short-lived and expires after 5 minutes. If you need to regenerate it, retrieve the `QueryRun` object again.

## Webhooks

Instead of polling, you can listen for webhooks to know when a query completes. Stripe sends a [v2.data.reporting.query_run.succeeded](https://docs.stripe.com/api/v2/data/reporting/query-runs/event-types.md?api-version=2026-04-22.preview#v2.data.reporting.query_run.succeeded) webhook when the query run completes successfully. In case of failure, Stripe sends a [v2.data.reporting.query_run.failed](https://docs.stripe.com/api/v2/data/reporting/query-runs/event-types.md?api-version=2026-04-22.preview#v2.data.reporting.query_run.failed) webhook instead. After receiving the webhook, retrieve the `QueryRun` and access the result URL as described above.

## Additional result options

### Request file compression

For large result sets, set `result_options.compress_file=true` to receive a zip-compressed output file.

```curl
curl -X POST https://api.stripe.com/v2/data/reporting/query_runs \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-04-22.preview" \
  --json '{
    "sql": "SELECT * FROM balance_transactions",
    "result_options": {
        "compress_file": true
    }
  }'
```

## Use an Organization API key

The Query Run API supports [Organization API keys](https://docs.stripe.com/keys.md#organization-api-keys).

### Run a query across your entire organization

When you use an Organization API key without a `Stripe-Context` header, the query runs in full organization mode—equivalent to running a query in [Sigma for Organizations](https://docs.stripe.com/stripe-data/sigma-organizations.md). In this mode, your query has access to data across all direct accounts in your organization, and an `account` column is available in data tables to identify which account each row belongs to.

```curl
curl -X POST https://api.stripe.com/v2/data/reporting/query_runs \
  -H "Authorization: Bearer {{ORG_SECRET_KEY}}" \
  -H "Stripe-Version: 2026-04-22.preview" \
  --json '{
    "sql": "SELECT account, id, amount FROM balance_transactions LIMIT 10"
  }'
```

### Run a query scoped to a single account

To run a query scoped to a single account in your organization, set the [Stripe-Context](https://docs.stripe.com/context.md) header to that account.

```curl
curl -X POST https://api.stripe.com/v2/data/reporting/query_runs \
  -H "Authorization: Bearer {{ORG_SECRET_KEY}}" \
  -H "Stripe-Version: 2026-04-22.preview" \
  -H "Stripe-Context: {{CONTEXT_ID}}" \
  --json '{
    "sql": "SELECT * FROM balance_transactions LIMIT 10"
  }'
```

## Limits

For general details about APIs in the v2 namespace, see the [API v2 overview](https://docs.stripe.com/api-v2-overview.md). For general API rate limits, see the [Rate limits](https://docs.stripe.com/rate-limits.md) page. Specific limits for the Query Run API include:

### Concurrent query runs

The API limits the number of `QueryRuns` that can be in `running` state simultaneously to 500 in livemode and 100 in testmode per account or organization. If you exceed this limit, the API responds with a status code of `429`. You can free up capacity by waiting for running queries to complete.

### Query execution timeout

Queries that exceed 90 minutes of execution time are automatically terminated and the `QueryRun` moves to `status=failed`.

### File size

The maximum supported file size for a query result is 5 GB. If you reach this limit, request compressed results by setting `result_options.compress_file=true`.

### File retention

Query run results are retained for 90 days.
