# Run Data Explorer queries with the Discourse API

**URL:** https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063
**Category:** Integrations
**Tags:** data-explorer, rest-api, how-to
**Created:** [June 11, 2019, 7:43pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063 "2019-06-11T19:43:38Z")
**Posts on this page:** 15
**Page:** 1

<div class="post-metadata">

### Author: ![Discourse](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/discourse/32/148734_2.png) [@Discourse](https://meta.discourse.org/u/Discourse)
#### Post date: [June 11, 2019, 7:43pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/1 "2019-06-11T19:43:38Z")

</div>

> 🔖 This guide explains how to use the Discourse API to create, run, and manage queries with the [Data Explorer](https://meta.discourse.org/t/32566?silent=true) plugin.
> 
> 🙋 Required user level: Administrator

Virtually any action that can be performed through the Discourse user interface can also be triggered with the Discourse API.

This document provides a comprehensive overview for utilizing the API specifically in conjunction with the [Data Explorer](https://meta.discourse.org/t/32566?silent=true) plugin.

For a general overview of how to find the correct API request for an action, see: [Reverse engineer the Discourse API](https://meta.discourse.org/t/how-to-reverse-engineer-the-discourse-api/20576) .

## Running a [Data Explorer](https://meta.discourse.org/t/32566?silent=true) query

To run a [Data Explorer](https://meta.discourse.org/t/32566?silent=true) query via the API, make a `POST` request to `/admin/plugins/discourse-data-explorer/queries/<query-id>/run`. You can find the query ID by visiting it through your Discourse site and checking the `id` parameter in the address bar.

Below is an example query with an ID of `20` that returns topics by views on a specified date:

```sql
--[params]
-- date :viewed_at

SELECT
topic_id,
COUNT(1) AS views_for_date
FROM topic_views
WHERE viewed_at = :viewed_at
GROUP BY topic_id
ORDER BY views_for_date DESC

```

This query can be run from a terminal with:

```bash
curl -X POST "https://your-site-url/admin/plugins/discourse-data-explorer/queries/20/run" \
-H "Content-Type: multipart/form-data;" \
-H "Api-Key: <api-key>" \
-H "Api-Username: system" \
-F 'params={"viewed_at":"2019-06-10"}'

```

Note that you’ll need to replace the `<api-key>`, `<your-site-url>` with your API key and domain.

## Handling large datasets

The [Data Explorer](https://meta.discourse.org/t/32566?silent=true) plugin limits JSON results to 1000 rows by default (controlled by the hidden `data_explorer_query_result_limit` site setting). You can override this per-request by passing a `limit` parameter, up to a maximum of 10,000:

```bash
curl -X POST "https://your-site-url/admin/plugins/discourse-data-explorer/queries/20/run" \
-H "Content-Type: multipart/form-data;" \
-H "Api-Key: <api-key>" \
-H "Api-Username: system" \
-F "limit=5000"

```

For datasets larger than 10,000 rows, you’ll need to paginate at the SQL level. You can use the example query below:

```sql
--[params]
-- integer :limit = 100
-- integer :page = 0
SELECT * 
FROM generate_series(1, 10000)
OFFSET :page * :limit 
LIMIT :limit

```

To fetch the results page-by-page, increment the `page` parameter in the request:

```bash
curl -X POST "https://your-site-url/admin/plugins/discourse-data-explorer/queries/27/run" \
-H "Content-Type: multipart/form-data;" \
-H "Api-Key: <api-key>" \
-H "Api-Username: system" \
-F 'params={"page":"0"}'

```

Stop when `result_count` is zero.

For additional information about handling large datasets, see: [Result Limits and Exporting Queries](https://meta.discourse.org/t/discourse-data-explorer/32566#p-138280-result-limits-and-exporting-queries-7)

## Removing `relations` data from the results

When [Data Explorer](https://meta.discourse.org/t/32566?silent=true) queries are run through the user interface, a `relations` object is added to the results. This data is used for rendering the user in UI results, but you are unlikely to need it when running queries via the API.

To remove that data from the results, add a `download=true` parameter with your request:

```bash
curl -X POST "https://your-site-url/admin/plugins/discourse-data-explorer/queries/27/run" \
-H "Content-Type: multipart/form-data;" \
-H "Api-Key: <api-key>" \
-H "Api-Username: system" \
-F 'params={"page":"0"}' \
-F "download=true"

```

## API authentication

Details about generating an API key for the requests can be found here: [Create and configure an API key](https://meta.discourse.org/t/create-and-configure-an-api-key/230124) .

If the API key is only going to be used to run [Data Explorer](https://meta.discourse.org/t/32566?silent=true) queries, you can select “Granular” from the Scope drop down menu, then select the “run queries” scope.

 ![Screenshot 2024-04-12 at 3.11.52 PM](https://global.discourse-cdn.com/meta/original/4X/d/e/0/de08dc117fcce3b54d59f22def342e04f9cbe301.png)

## FAQs

> _Is there any api endpoint I can use to get the list of reports and the ID numbers? I want to build a dropdown with the list in it?_

Yes, you can make an authenticated GET request to `/admin/plugins/discourse-data-explorer/queries.json` to get a list of all queries on the site.

> _Is it possible to create queries through the api?_

Yes. Documentation on how to do that are at [Create a Data Explorer query using the API](https://meta.discourse.org/t/create-a-data-explorer-query-using-the-api/394388)

> _Is it possible to send parameters with the post request?_

Yes, include SQL parameters using the `-F` option, as shown in the examples.

> _Is CSV export for queries supported by the API?_

Yes. Append `.csv` to the run endpoint URL to get results in CSV format:

```bash
curl -X POST "https://your-site-url/admin/plugins/discourse-data-explorer/queries/20/run.csv" \
-H "Api-Key: <api-key>" \
-H "Api-Username: system" \
-F 'params={"viewed_at":"2019-06-10"}'

```

CSV responses default to returning up to 10,000 rows. You can pass a `limit` parameter to reduce this.

## Additional resources

- [Create a Data Explorer query using the API](https://meta.discourse.org/t/create-a-data-explorer-query-using-the-api/394388)
- [Create and configure an API key](https://meta.discourse.org/t/create-and-configure-an-api-key/230124)
- [Reverse engineer the Discourse API](https://meta.discourse.org/t/how-to-reverse-engineer-the-discourse-api/20576)
- [GitHub - lee-dohm/execute-discourse-query: Executes a Discourse data-explorer query via the API · GitHub](https://github.com/lee-dohm/execute-discourse-query)

---

<div class="post-metadata">

### Author: ![37Rb](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/37rb/32/224158_2.png) [@37Rb](https://meta.discourse.org/u/37Rb)
#### Post date: [May 10, 2024, 2:47am UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/52 "2024-05-10T02:47:02Z")

</div>

> [@simon](#):
>
> The `limit=ALL` support for CSV exports

This comment seems to imply that you can do CSV export from the API. Is that possible? Just curious because I need the data as CSV. I can always get it as JSON and convert to CSV but if there is a built-in way to get CSV that would be a little easier.

---

<div class="post-metadata">

### Author: ![francis\_exalate](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/francis_exalate/32/461390_2.png) [@francis\_exalate](https://meta.discourse.org/u/francis_exalate)
#### Post date: [January 4, 2025, 1:50pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/53 "2025-01-04T13:50:10Z")

</div>

Is it possible to do a query ‘like created or updated the last 50 seconds’?

---

<div class="post-metadata">

### Author: ![Canapin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/canapin/32/119591_2.png) [@Canapin](https://meta.discourse.org/u/Canapin)
#### Post date: [January 4, 2025, 2:21pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/54 "2025-01-04T14:21:31Z")

</div>

🤖 AI says

```sql
-- [params]
-- int :seconds = 50

SELECT
    p.id AS post_id,
    p.created_at,
    p.updated_at,
    p.raw AS post_content,
    p.user_id,
    t.title AS topic_title,
    t.id AS topic_id
FROM posts p
INNER JOIN topics t ON t.id = p.topic_id
WHERE
    (EXTRACT(EPOCH FROM (NOW() - p.created_at)) <= :seconds
    OR EXTRACT(EPOCH FROM (NOW() - p.updated_at)) <= :seconds)
    AND p.deleted_at IS NULL
    AND t.deleted_at IS NULL
ORDER BY p.created_at DESC
LIMIT 50

```

Did a quick test, it seems to work 🙂

---

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [January 4, 2025, 2:34pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/55 "2025-01-04T14:34:17Z")

</div>

I don’t see the “like ❤” I added to a post using your query.

I think you need to use `post_actions`

```sql
--[params]
--string :timespan = 50 seconds

SELECT post_id,
       user_id, 
       created_at, 
       updated_at, 
       deleted_at
FROM post_actions
WHERE post_action_type_id=2 AND updated_at > NOW() - INTERVAL :timespan

```

Version with more beautiful results

```sql
--[params]
--string :timespan = 50 seconds
--boolean :include_in_timespan_deleted = false

SELECT 
  post_id, 
  user_id, 
  CASE
    WHEN EXTRACT(EPOCH FROM (NOW() - created_at)) < 60 THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - created_at))), ' seconds ago')
    WHEN EXTRACT(EPOCH FROM (NOW() - created_at)) < 3600 THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - created_at)) / 60), ' minutes ago')
    WHEN EXTRACT(EPOCH FROM (NOW() - created_at)) < 86400 THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - created_at)) / 3600), ' hours ago')
    ELSE CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - created_at)) / 86400), ' days ago')
  END AS relative_created_at,
  CASE
    WHEN EXTRACT(EPOCH FROM (NOW() - updated_at)) < 60 THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - updated_at))), ' seconds ago')
    WHEN EXTRACT(EPOCH FROM (NOW() - updated_at)) < 3600 THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - updated_at)) / 60), ' minutes ago')
    WHEN EXTRACT(EPOCH FROM (NOW() - updated_at)) < 86400 THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - updated_at)) / 3600), ' hours ago')
    ELSE CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - updated_at)) / 86400), ' days ago')
  END AS relative_updated_at,
  CASE
    WHEN deleted_at IS NULL THEN 'no'
    WHEN EXTRACT(EPOCH FROM (NOW() - deleted_at)) < 60 THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - deleted_at))), ' seconds ago')
    WHEN EXTRACT(EPOCH FROM (NOW() - deleted_at)) < 3600 THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - deleted_at)) / 60), ' minutes ago')
    WHEN EXTRACT(EPOCH FROM (NOW() - deleted_at)) < 86400 THEN CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - deleted_at)) / 3600), ' hours ago')
    ELSE CONCAT(ROUND(EXTRACT(EPOCH FROM (NOW() - deleted_at)) / 86400), ' days ago')
  END AS relative_deleted_at
FROM 
  post_actions
WHERE 
  post_action_type_id = 2 
  AND updated_at > NOW() - INTERVAL :timespan
  AND (
    :include_in_timespan_deleted = false 
    OR (deleted_at IS NOT NULL AND deleted_at > NOW() - INTERVAL :timespan)
  )

```

---

<div class="post-metadata">

### Author: ![Canapin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/canapin/32/119591_2.png) [@Canapin](https://meta.discourse.org/u/Canapin)
#### Post date: [January 4, 2025, 3:37pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/56 "2025-01-04T15:37:02Z")

</div>

Oooh I misread! I read the sentence as "Is it possible to do a query like ‘created or updated the last 50 seconds’?

(notice the single quote position)

---

<div class="post-metadata">

### Author: ![francis\_exalate](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/francis_exalate/32/461390_2.png) [@francis\_exalate](https://meta.discourse.org/u/francis_exalate)
#### Post date: [January 4, 2025, 6:58pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/57 "2025-01-04T18:58:44Z")

</div>

Thanks all,

I posted here because that is where ask.discourse pointed me to.  
What I’m looking for is if the API is providing this capability.

---

<div class="post-metadata">

### Author: ![Canapin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/canapin/32/119591_2.png) [@Canapin](https://meta.discourse.org/u/Canapin)
#### Post date: [January 4, 2025, 7:09pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/58 "2025-01-04T19:09:12Z")

</div>

Yes, of course.

You create the query in [data explorer](https://meta.discourse.org/t/32566?silent=true), then you run the query through the API as described in this guide.

I just ran Moin’s query via the API and it properly returned the expected results.

 ![The image shows a JSON response from an API request with a status code of 200 OK, containing information about a post with a time span of 9999 seconds and a result count of 1. (Captioned by AI)](https://global.discourse-cdn.com/meta/original/4X/a/7/9/a7953846fa63b74e92d5a501a4886abc2cdd45d2.png)

---

<div class="post-metadata">

### Author: ![tanya\_byrne](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/tanya_byrne/32/444826_2.png) [@tanya\_byrne](https://meta.discourse.org/u/tanya_byrne)
#### Post date: [January 14, 2025, 10:57pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/59 "2025-01-14T22:57:30Z")

</div>

I was wondering about this too. Is JSON the only way of exporting data via the API or is CSV export also supported for [Data Explorer](https://meta.discourse.org/t/32566?silent=true)?

---

<div class="post-metadata">

### Author: ![francis\_exalate](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/francis_exalate/32/461390_2.png) [@francis\_exalate](https://meta.discourse.org/u/francis_exalate)
#### Post date: [January 19, 2025, 11:18pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/60 "2025-01-19T23:18:52Z")

</div>

Thanks all,

Sorry for the late response - was offline for a bit.

What I’m currently doing is to search for all topics/posts created today, and filter out the topics/posts updated before the timestamp.

---

<div class="post-metadata">

### Author: ![Canapin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/canapin/32/119591_2.png) [@Canapin](https://meta.discourse.org/u/Canapin)
#### Post date: [January 19, 2025, 11:35pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/61 "2025-01-19T23:35:44Z")

</div>

> [@francis\_exalate](#):
>
> What I’m currently doing is to search for all topics/posts created today, and filter out the topics/posts updated before the timestamp.

If you don’t want to get your hands dirty, you can ask the bot at [ask.discourse.com](http://ask.discourse.com). It’s usually pretty accurate regarding Discourse-related SQL queries (but don’t _assume_ it’s right, check the code to be sure).

---

<div class="post-metadata">

### Author: ![srinivas.chilukuri](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/srinivas.chilukuri/32/341708_2.png) [@srinivas.chilukuri](https://meta.discourse.org/u/srinivas.chilukuri)
#### Post date: [May 6, 2025, 4:06pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/62 "2025-05-06T16:06:20Z")

</div>

> [@Discourse](#):
>
> `-H "Content-Type: multipart/form-data;" \`

* * *

**Is the `Content-Type` header correct?**  
In the developer tools, when inspecting a [Data Explorer](https://meta.discourse.org/t/32566?silent=true) query with params , the `Content-Type` header appears as:

```plaintext
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

```

However, the current cURL command includes:

```plaintext
-H "Content-Type: multipart/form-data;"

```

* * *

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [May 6, 2025, 4:30pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/63 "2025-05-06T16:30:13Z")

</div>

- `multipart/form-data`
- `application/x-www-form-urlencoded`
- `application/json`

are all valid content-types you can use when making an api request.

---

<div class="post-metadata">

### Author: ![srinivas.chilukuri](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/srinivas.chilukuri/32/341708_2.png) [@srinivas.chilukuri](https://meta.discourse.org/u/srinivas.chilukuri)
#### Post date: [May 6, 2025, 4:37pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/64 "2025-05-06T16:37:01Z")

</div>

@blake  
language python  
library requests  
Could you provide a API sample [Data Explorer](https://meta.discourse.org/t/32566?silent=true) query that includes **three parameters  
refer** [Topic]( [Passing params to Data Explorer using API requires enclosing a value](https://meta.discourse.org/t/passing-params-to-data-explorer-using-api-requires-enclosing-a-value/322843) ) which says params need to be strictly in double quotes

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [May 6, 2025, 5:15pm UTC](https://meta.discourse.org/t/run-data-explorer-queries-with-the-discourse-api/120063/65 "2025-05-06T17:15:01Z")

</div>

Sure, here is an example using python:

```python
import json
import requests

API_KEY = "YOUR_API_KEY"
API_USERNAME = "system"
QUERY_ID = 20
SITE_URL = "https://your-site-url"

# all values must be strings
params = {
    "user_id": "2",
    "viewed_at": "2019-06-10",
    "limit": "5"
}

# Data Explorer expects params as a JSON‐encoded string
payload = {
    "params": json.dumps(params)
}

url = f"{SITE_URL}/admin/plugins/explorer/queries/{QUERY_ID}/run"
headers = {
    "Api-Key": API_KEY,
    "Api-Username": API_USERNAME,
    "Content-Type": "application/json"
}

r = requests.post(url, headers=headers, json=payload)
r.raise_for_status()
print(r.json())

```
