> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Apache Superset

> Client-mode REST API connector for Apache Superset: run chart queries and SQL Lab statements and publish the rows, upload CSV rollups into a database, register and refresh datasets, warm chart caches, mark downtime windows as chart annotations, read alert runs and issue guest tokens for embedded HMI dashboards, with a JWT from the Superset login API

Technical Preview

## Overview

A client-mode connector for the [Apache Superset REST API](https://superset.apache.org/docs/api). Use it to put BI results on MQTT and plant events into Superset: run a chart query or a SQL Lab statement and publish the rows to a display, upload a CSV rollup (shift totals, OEE per line) into a database, register and refresh datasets, warm the chart cache when new data lands, and mark downtime, changeover or alarm windows as annotations on time-series charts. It also reads alerts and reports with their run logs and issues guest tokens for dashboards embedded on an HMI or kiosk.

The connector `ApacheSupersetRestApi` in `ApacheSupersetRestApi.lot` has 38 events (26 GET, 9 POST, 3 PUT). 9 are writes: `DatasetCreate`, `DatasetRefresh`, `DatasetWarmUpCache`, `DatabaseCsvUpload`, `SqlLabExecute`, `AnnotationLayerCreate`, `AnnotationCreate`, `AnnotationUpdate`, `GuestTokenCreate`. There are no DELETE events.

Set `BASE_ADDRESS` to your Superset web root with no trailing slash, for example `https://superset.example.com` (use `http://` and `USE_SSL "false"` for a plain-HTTP install on a LAN). Every endpoint already starts with `/api/v1`, except `/health`. Topics use `analytics/apache-superset/<object>/<verb>`; replies land on `<topic>/response`.

See the base guide: [REST API Connectors](/latest/lot-language/routes/rest-api-routes).

> [!NOTE]
>
> **Auth:** publish `{"body":{"username":"...","password":"...","provider":"db","refresh":true}}` to `analytics/apache-superset/auth/login` (`provider` is `ldap` for LDAP users). The reply carries a JWT `access_token`, which expires after 15 minutes by default, and a `refresh_token` when `refresh` is true. Every event except `HealthGet` and `SecurityLogin` sends `Authorization: Bearer <access_token>`. To renew, publish `{"access_token":"<refresh_token>"}` to `analytics/apache-superset/auth/refresh`, or log in again. Use a dedicated Superset user with only the roles the broker needs. Keep `SUPERSET_USER` and `SUPERSET_PASSWORD` in broker secrets, read them with `GET SECRET`, never retain topics that carry a password or token, and restrict `analytics/apache-superset/auth/#` and `analytics/apache-superset/embedded/#` with ACLs. Deployments that only allow OAuth or SAML sign-in need a database or LDAP service user for the API.

> [!WARNING]
>
> **CSRF on writes.** Superset 6.x checks a CSRF token on API `POST` and `PUT` calls even when they carry a JWT, and the token only works together with a browser session cookie, which a broker connector does not keep. Without a change, every write except `ChartDataQuery` answers `400 The CSRF token is missing`. Reads, `SecurityLogin`, `SecurityRefresh` and `ChartDataQuery` work as is. To allow the packaged writes, add their views to `WTF_CSRF_EXEMPT_LIST` in `superset_config.py` and restart Superset. This exempts only those views; a signed-in browser could then be tricked into calling them, so keep the list to the writes you use.

```python theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
import os
from superset.config import WTF_CSRF_EXEMPT_LIST as _DEFAULT_EXEMPT
SECRET_KEY = os.environ["SUPERSET_SECRET_KEY"]
PREVENT_UNSAFE_DB_CONNECTIONS = False
FEATURE_FLAGS = {"ALERT_REPORTS": True, "EMBEDDED_SUPERSET": True}
# Coreflux ApacheSupersetRestApi route: API writes with a JWT and no browser session.
WTF_CSRF_EXEMPT_LIST = [*_DEFAULT_EXEMPT,
    "superset.datasets.api.post", "superset.datasets.api.refresh", "superset.datasets.api.warm_up_cache",
    "superset.databases.api.upload", "superset.sqllab.api.execute_sql_query",
    "superset.annotation_layers.api.post", "superset.annotation_layers.annotations.api.post",
    "superset.annotation_layers.annotations.api.put", "superset.security.api.guest_token"]
GUEST_TOKEN_JWT_SECRET = os.environ["SUPERSET_SECRET_KEY"] + "-guest"
```

## Events

| Event | Request | Source topic | Notes |
| - | - | - | - |
| HealthGet | `GET /health` | `analytics/apache-superset/health/get` | Smoke without login: answers OK when the web server is up. No Authorization header. |
| SecurityLogin | `POST /api/v1/security/login` | `analytics/apache-superset/auth/login` | Log in and get a JWT access\_token (and refresh\_token with refresh:true). No Authorization header. |
| SecurityRefresh | `POST /api/v1/security/refresh` | `analytics/apache-superset/auth/refresh` | New access\_token from a refresh token: send the refresh token as access\_token in the payload (Authorization: Bearer \<refresh\_token>). No body. |
| CsrfTokenGet | `GET /api/v1/security/csrf_token/` | `analytics/apache-superset/auth/csrf_token` | CSRF token, only needed when the server enforces CSRF on API writes (see the docs page). Not needed with the default JWT setup tested here. |
| MeGet | `GET /api/v1/me/` | `analytics/apache-superset/me/get` | Smoke: the user behind the token (id, username, first\_name, is\_active). |
| MeRolesGet | `GET /api/v1/me/roles/` | `analytics/apache-superset/me/roles` | Roles and permissions of the token user. |
| DashboardsList | `GET /api/v1/dashboard/?{value.json.query}` | `analytics/apache-superset/dashboards/list` | Dashboards. |
| DashboardGet | `GET /api/v1/dashboard/{value.json.dashboard_id}` | `analytics/apache-superset/dashboards/get` | One dashboard by id or slug (title, url, owners, json\_metadata). |
| DashboardChartsList | `GET /api/v1/dashboard/{value.json.dashboard_id}/charts` | `analytics/apache-superset/dashboards/charts` | Charts on a dashboard (ids, form\_data). |
| DashboardDatasetsList | `GET /api/v1/dashboard/{value.json.dashboard_id}/datasets` | `analytics/apache-superset/dashboards/datasets` | Datasets behind a dashboard. |
| ChartsList | `GET /api/v1/chart/?{value.json.query}` | `analytics/apache-superset/charts/list` | Charts. |
| ChartGet | `GET /api/v1/chart/{value.json.chart_id}` | `analytics/apache-superset/charts/get` | One chart (viz\_type, params, query\_context). |
| ChartDataGet | `GET /api/v1/chart/{value.json.chart_id}/data/?{value.json.query}` | `analytics/apache-superset/charts/data_get` | Result of a saved chart's query (catalog: chart result to MQTT). |
| ChartDataQuery | `POST /api/v1/chart/data` | `analytics/apache-superset/charts/data_query` | Run an ad hoc query context against a dataset and publish the rows (catalog event: POST chart/data). Read-only. |
| DatasetsList | `GET /api/v1/dataset/?{value.json.query}` | `analytics/apache-superset/datasets/list` | Datasets. |
| DatasetGet | `GET /api/v1/dataset/{value.json.dataset_id}` | `analytics/apache-superset/datasets/get` | One dataset (columns, metrics, database, table\_name). |
| DatasetCreate | `POST /api/v1/dataset/` | `analytics/apache-superset/datasets/create` | Write. Register a table or view as a dataset. |
| DatasetRefresh | `PUT /api/v1/dataset/{value.json.dataset_id}/refresh` | `analytics/apache-superset/datasets/refresh` | Write (re-reads column metadata from the source table). Catalog event 'POST dataset/(id)/refresh' (the API method is PUT). No body. |
| DatasetWarmUpCache | `PUT /api/v1/dataset/warm_up_cache` | `analytics/apache-superset/datasets/warm_up_cache` | Write (fills the chart cache). Warm the cache for every chart on a table after new data lands. |
| DatabasesList | `GET /api/v1/database/?{value.json.query}` | `analytics/apache-superset/databases/list` | Database connections (no passwords). |
| DatabaseGet | `GET /api/v1/database/{value.json.database_id}` | `analytics/apache-superset/databases/get` | One database connection (backend, allow\_dml, allow\_file\_upload). |
| DatabaseSchemasList | `GET /api/v1/database/{value.json.database_id}/schemas/?{value.json.query}` | `analytics/apache-superset/databases/schemas` | Schemas of a database. |
| DatabaseTablesList | `GET /api/v1/database/{value.json.database_id}/tables/?{value.json.query}` | `analytics/apache-superset/databases/tables` | Tables in a schema. |
| DatabaseCsvUpload | `POST /api/v1/database/{value.json.database_id}/upload/` | `analytics/apache-superset/databases/upload_csv` | Write (creates or appends a table). Upload a CSV rollup (shift totals, OEE per line) into a database that allows file upload. |
| SqlLabExecute | `POST /api/v1/sqllab/execute/` | `analytics/apache-superset/sqllab/execute` | Write only when the database allows DML. Run SQL and publish the rows. |
| SqlLabResultsGet | `GET /api/v1/sqllab/results/?{value.json.query}` | `analytics/apache-superset/sqllab/results` | Results of an async SQL Lab query. |
| QueriesList | `GET /api/v1/query/?{value.json.query}` | `analytics/apache-superset/queries/list` | SQL Lab query history (status, rows, error). |
| SavedQueriesList | `GET /api/v1/saved_query/?{value.json.query}` | `analytics/apache-superset/saved_queries/list` | Saved SQL Lab queries. |
| ReportsList | `GET /api/v1/report/?{value.json.query}` | `analytics/apache-superset/reports/list` | Alerts and reports (needs ALERT\_REPORTS feature flag). |
| ReportGet | `GET /api/v1/report/{value.json.report_id}` | `analytics/apache-superset/reports/get` | One alert or report (crontab, recipients, last\_state). |
| ReportLogsList | `GET /api/v1/report/{value.json.report_id}/log/?{value.json.query}` | `analytics/apache-superset/reports/logs` | Execution log of an alert or report (state, error\_message, end\_dttm). |
| AnnotationLayersList | `GET /api/v1/annotation_layer/?{value.json.query}` | `analytics/apache-superset/annotation_layers/list` | Annotation layers (overlays on time-series charts). |
| AnnotationLayerCreate | `POST /api/v1/annotation_layer/` | `analytics/apache-superset/annotation_layers/create` | Write. Create a layer for plant events. |
| AnnotationsList | `GET /api/v1/annotation_layer/{value.json.layer_id}/annotation/?{value.json.query}` | `analytics/apache-superset/annotations/list` | Annotations in a layer. |
| AnnotationCreate | `POST /api/v1/annotation_layer/{value.json.layer_id}/annotation/` | `analytics/apache-superset/annotations/create` | Write. Mark a downtime, changeover or alarm window on time-series charts. |
| AnnotationUpdate | `PUT /api/v1/annotation_layer/{value.json.layer_id}/annotation/{value.json.annotation_id}` | `analytics/apache-superset/annotations/update` | Write. Close an open window (set end\_dttm) or fix its text. body e.g. ("end\_dttm":"2026-10-05T08:12:00"). |
| LogRecentActivity | `GET /api/v1/log/recent_activity/?{value.json.query}` | `analytics/apache-superset/logs/recent_activity` | Recent views and edits of the token user. |
| GuestTokenCreate | `POST /api/v1/security/guest_token/` | `analytics/apache-superset/embedded/guest_token` | Write (issues a token). Guest token for an embedded dashboard on an HMI or kiosk (needs EMBEDDED\_SUPERSET and the can\_grant\_guest\_token permission). |

Payload fields: `access_token`; ids `dashboard_id` (id or slug), `chart_id`, `dataset_id`, `database_id`, `report_id`, `layer_id`, `annotation_id`; `query` (URL query string without `?`, or `""`; list endpoints take Rison in `q`, for example `q=(page_size:20)`); `body` (JSON); `multipart` (whole `multipart/form-data` body for `DatabaseCsvUpload`).

## Basic Syntax

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE ApacheSupersetRestApi WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://superset.example.com"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '120'
        WITH DEFAULT_CONTENT_TYPE "application/json"
    ADD EVENT SecurityLogin
        WITH SOURCE_TOPIC "analytics/apache-superset/auth/login"
        WITH DESTINATION_TOPIC "analytics/apache-superset/auth/login/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/api/v1/security/login"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    ADD EVENT MeGet
        WITH SOURCE_TOPIC "analytics/apache-superset/me/get"
        WITH DESTINATION_TOPIC "analytics/apache-superset/me/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/api/v1/me/"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Authorization" "Bearer {value.json.access_token}"
    ADD EVENT ChartDataQuery
        WITH SOURCE_TOPIC "analytics/apache-superset/charts/data_query"
        WITH DESTINATION_TOPIC "analytics/apache-superset/charts/data_query/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/api/v1/chart/data"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Authorization" "Bearer {value.json.access_token}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    ADD EVENT DatabaseCsvUpload
        WITH SOURCE_TOPIC "analytics/apache-superset/databases/upload_csv"
        WITH DESTINATION_TOPIC "analytics/apache-superset/databases/upload_csv/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/api/v1/database/{value.json.database_id}/upload/"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Authorization" "Bearer {value.json.access_token}"
            ADD "Content-Type" "multipart/form-data; boundary=CorefluxSupersetBoundary"
        WITH BODY "{value.json.multipart}"
    ADD EVENT AnnotationCreate
        WITH SOURCE_TOPIC "analytics/apache-superset/annotations/create"
        WITH DESTINATION_TOPIC "analytics/apache-superset/annotations/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/api/v1/annotation_layer/{value.json.layer_id}/annotation/"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Authorization" "Bearer {value.json.access_token}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
```

## Examples

### Keep a token for the other actions

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION SupersetLogin
ON EVERY 10 MINUTES DO
    SET "user" WITH GET SECRET "SUPERSET_USER"
    SET "password" WITH GET SECRET "SUPERSET_PASSWORD"
    PUBLISH TOPIC "analytics/apache-superset/auth/login" WITH { "body": { "username": {user}, "password": {password}, "provider": "db", "refresh": false } }

DEFINE ACTION SupersetKeepToken
ON TOPIC "analytics/apache-superset/auth/login/response" DO
    SET "token" WITH GET JSON "access_token" IN PAYLOAD AS STRING
    KEEP TOPIC "state/superset/access_token" WITH {token}
```

### Mark a downtime window on charts

Create a layer once with `AnnotationLayerCreate`, then add an annotation when a stop starts and close it with `AnnotationUpdate` when the line runs again. Times are local timestamps without a zone, for example `2026-10-05T08:00:00`.

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION SupersetMarkDowntime
ON TOPIC "plant/line2/downtime/start" DO
    SET "token" WITH GET TOPIC "state/superset/access_token" AS STRING
    SET "start" WITH GET JSON "start" IN PAYLOAD AS STRING
    PUBLISH TOPIC "analytics/apache-superset/annotations/create" WITH { "access_token": {token}, "layer_id": "3", "body": { "short_descr": "Line 2 stop", "start_dttm": {start}, "end_dttm": {start} } }
```

The reply on `analytics/apache-superset/annotations/create/response` carries the annotation `id`. Publish `{"access_token":"...","layer_id":"3","annotation_id":"<id>","body":{"end_dttm":"2026-10-05T08:12:00"}}` to `analytics/apache-superset/annotations/update` to close the window.

### Publish chart rows to a display

`ChartDataQuery` runs a query context against a dataset and returns the rows in `result[0].data`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token":"...","body":{"datasource":{"id":4,"type":"table"},"queries":[{"columns":["line"],"metrics":[{"expressionType":"SQL","sqlExpression":"SUM(units)","label":"units"}],"row_limit":100}],"result_format":"json","result_type":"full"}}
```

For a saved chart, publish `{"access_token":"...","chart_id":"12","query":"format=json&type=full"}` to `analytics/apache-superset/charts/data_get`. The chart needs a saved query context (charts saved from the Explore view in recent versions have one).

### Upload a CSV rollup

`DatabaseCsvUpload` sends `multipart` unchanged with `Content-Type: multipart/form-data; boundary=CorefluxSupersetBoundary`. Use CRLF line endings. The database needs **Allow file uploads to database** and the schema must be allowed for uploads. Superset 6.x registers the new table as a dataset itself.

```text theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
--CorefluxSupersetBoundary
Content-Disposition: form-data; name="type"

csv
--CorefluxSupersetBoundary
Content-Disposition: form-data; name="table_name"

shift_rollup
--CorefluxSupersetBoundary
Content-Disposition: form-data; name="schema"

main
--CorefluxSupersetBoundary
Content-Disposition: form-data; name="already_exists"

append
--CorefluxSupersetBoundary
Content-Disposition: form-data; name="file"; filename="rollup.csv"
Content-Type: text/csv

line,shift,units,oee
Line 1,A,1200,0.81
--CorefluxSupersetBoundary--
```

### SQL Lab

`SqlLabExecute` runs SQL synchronously with `"runAsync":false` and returns the rows in `data`. Keep **Allow DML** off on the connection so the connector can only read: a mutating statement then fails with `This database does not allow for DDL/DML`. `SqlLabResultsGet` only applies to async queries with a results backend.

## Not packaged

* DELETE operations, on purpose.
* Alert and report create and update: those views share one wrapped handler with report deletes and plugin forms, so they cannot be CSRF-exempted on their own. Create alerts in the Superset UI; the connector reads them with `ReportsList`, `ReportGet` and `ReportLogsList`.
* Import and export ZIPs, screenshots and thumbnails, security, role and user administration, row-level security, themes, tags, explore form data and permalinks.

## Testing status

Live HTTP smoke against a throwaway Apache Superset 6.1.0 (official `apache/superset` Docker image, SQLite metadata and data, `ALERT_REPORTS` and `EMBEDDED_SUPERSET` on, the `WTF_CSRF_EXEMPT_LIST` above): static 312/312, spec 121/121, live 62/62, controls 36/36. Every event ran with valid credentials: CSV upload into a new table with a non-ASCII value, chart data and SQL Lab rows matching the upload, a refused DML statement, dataset refresh and cache warm-up, annotation create and update, dashboard by slug, alert read-back and a guest token for an embedded dashboard. Every authenticated event answered `401` or `422` with an invalid token, and a wrong password answered `401`. Superset rate-limits by client: 50 requests per second overall and 5 login attempts per second by default (`429` beyond that).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.