> ## 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.

# Tableau (Server and Cloud)

> Client-mode REST API connector for Tableau Server and Tableau Cloud: sign in with a personal access token, pull view data as CSV, run extract refresh tasks or refresh data sources and workbooks on demand, poll jobs, and list workbooks, views and data sources

Technical Preview

## Overview

A client-mode connector for the [Tableau REST API](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api.htm) on Tableau Server and Tableau Cloud. Use it to refresh a dashboard's extract as soon as a shift, batch or line event lands on the broker, instead of waiting for the next schedule, and to pull summarized view data (OEE, scrap, downtime by line) back into MQTT as CSV.

| Event | Request | Notes |
| - | - | - |
| ServerInfo | `GET /api/3.24/serverinfo` | No auth. Smoke and version check (`restApiVersion`) |
| SignIn | `POST /api/3.24/auth/signin` | PAT credentials in the MQTT payload; returns `token` and the site LUID |
| SignOut | `POST /api/3.24/auth/signout` | Ends the session; `204` |
| ListWorkbooks | `GET /api/3.24/sites/{site}/workbooks?pageSize=&pageNumber=` | Paged list |
| ListViews | `GET /api/3.24/sites/{site}/views?pageSize=&pageNumber=` | Find view LUIDs |
| GetViewData | `GET /api/3.24/sites/{site}/views/{view}/data?maxAge=` | `200` `text/csv` |
| GetViewDataFiltered | `GET .../views/{view}/data?maxAge=&vf_<field>=<value>` | One view filter |
| ListDataSources | `GET /api/3.24/sites/{site}/datasources?pageSize=&pageNumber=` | Published data sources |
| RefreshDataSource | `POST /api/3.24/sites/{site}/datasources/{ds}/refresh` | Body `{}`; `202` with a job |
| RefreshWorkbook | `POST /api/3.24/sites/{site}/workbooks/{wb}/refresh` | Body `{}`; `202` with a job |
| ListExtractRefreshTasks | `GET /api/3.24/sites/{site}/tasks/extractRefreshes` | Task ids for RunExtractRefreshTask |
| RunExtractRefreshTask | `POST /api/3.24/sites/{site}/tasks/extractRefreshes/{task}/runNow` | Body `{}`; `200` with a job |
| GetJob | `GET /api/3.24/sites/{site}/jobs/{job}` | `finishCode` 0 = success, 1 = error, 2 = cancelled |

Set `BASE_ADDRESS` to your Tableau host with no trailing slash and no `/api`. On Tableau Cloud the host must include the pod, for example `https://prod-useast-a.online.tableau.com` or `https://10ax.online.tableau.com` (copy it from the browser address bar). Topics use `analytics/tableau/<object>/<verb>`; replies land on `<topic>/response`.

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

> [!NOTE]
>
> **Auth:** sign in once with a **personal access token** (PAT), then send `X-Tableau-Auth: {value.json.auth_token}` on every other call together with `site_id`, the site **LUID** from the sign-in response (not the site name or `contentUrl`). A session lasts 240 minutes on Tableau Server (default) and 120 minutes on Tableau Cloud. Keep the PAT in Hub secrets (suggested names `TABLEAU_PAT_NAME` and `TABLEAU_PAT_SECRET`, and `TABLEAU_AUTH_TOKEN` for the current session token). Never commit them into the `.lot`. Signing in again with the same PAT ends that PAT's earlier session, so give each broker or connector its own PAT.

> [!NOTE]
>
> **API version.** Every endpoint uses `/api/3.24`. That works on Tableau Cloud and on Tableau Server 2025.1 or later. For Tableau Server 2024.2 change `3.24` to `3.23` (2023.3 uses `3.21`). `ServerInfo` returns the highest version your server supports.

## Basic Syntax

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE TableauRestApi WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://YOUR_TABLEAU_HOST"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '120'
        WITH DEFAULT_CONTENT_TYPE "application/json"
        # Smoke: GET /api/3.24/serverinfo → 200 (no auth). Auth: X-Tableau-Auth {value.json.auth_token}. Never commit PATs or tokens.
    ADD EVENT ServerInfo
        WITH SOURCE_TOPIC "analytics/tableau/server/info"
        WITH DESTINATION_TOPIC "analytics/tableau/server/info/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/api/3.24/serverinfo"
        WITH HEADERS
            ADD "Accept" "application/json"
    ADD EVENT SignIn
        WITH SOURCE_TOPIC "analytics/tableau/auth/signin"
        WITH DESTINATION_TOPIC "analytics/tableau/auth/signin/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/api/3.24/auth/signin"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
        WITH BODY "{payload}"
    ADD EVENT SignOut
        WITH SOURCE_TOPIC "analytics/tableau/auth/signout"
        WITH DESTINATION_TOPIC "analytics/tableau/auth/signout/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/api/3.24/auth/signout"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
    ADD EVENT ListWorkbooks
        WITH SOURCE_TOPIC "analytics/tableau/workbooks/list"
        WITH DESTINATION_TOPIC "analytics/tableau/workbooks/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/api/3.24/sites/{value.json.site_id}/workbooks?pageSize={value.json.page_size}&pageNumber={value.json.page_number}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
    ADD EVENT ListViews
        WITH SOURCE_TOPIC "analytics/tableau/views/list"
        WITH DESTINATION_TOPIC "analytics/tableau/views/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/api/3.24/sites/{value.json.site_id}/views?pageSize={value.json.page_size}&pageNumber={value.json.page_number}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
    ADD EVENT GetViewData
        WITH SOURCE_TOPIC "analytics/tableau/views/data/get"
        WITH DESTINATION_TOPIC "analytics/tableau/views/data/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/api/3.24/sites/{value.json.site_id}/views/{value.json.view_id}/data?maxAge={value.json.max_age}"
        WITH HEADERS
            ADD "Accept" "text/csv"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
    ADD EVENT GetViewDataFiltered
        WITH SOURCE_TOPIC "analytics/tableau/views/data/filter"
        WITH DESTINATION_TOPIC "analytics/tableau/views/data/filter/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/api/3.24/sites/{value.json.site_id}/views/{value.json.view_id}/data?maxAge={value.json.max_age}&vf_{value.json.filter_field}={value.json.filter_value}"
        WITH HEADERS
            ADD "Accept" "text/csv"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
    ADD EVENT ListDataSources
        WITH SOURCE_TOPIC "analytics/tableau/datasources/list"
        WITH DESTINATION_TOPIC "analytics/tableau/datasources/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/api/3.24/sites/{value.json.site_id}/datasources?pageSize={value.json.page_size}&pageNumber={value.json.page_number}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
    ADD EVENT RefreshDataSource
        WITH SOURCE_TOPIC "analytics/tableau/datasources/refresh"
        WITH DESTINATION_TOPIC "analytics/tableau/datasources/refresh/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/api/3.24/sites/{value.json.site_id}/datasources/{value.json.datasource_id}/refresh"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
        WITH BODY "{value.json.body}"
    ADD EVENT RefreshWorkbook
        WITH SOURCE_TOPIC "analytics/tableau/workbooks/refresh"
        WITH DESTINATION_TOPIC "analytics/tableau/workbooks/refresh/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/api/3.24/sites/{value.json.site_id}/workbooks/{value.json.workbook_id}/refresh"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
        WITH BODY "{value.json.body}"
    ADD EVENT ListExtractRefreshTasks
        WITH SOURCE_TOPIC "analytics/tableau/tasks/extract-refreshes/list"
        WITH DESTINATION_TOPIC "analytics/tableau/tasks/extract-refreshes/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/api/3.24/sites/{value.json.site_id}/tasks/extractRefreshes"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
    ADD EVENT RunExtractRefreshTask
        WITH SOURCE_TOPIC "analytics/tableau/tasks/extract-refreshes/run"
        WITH DESTINATION_TOPIC "analytics/tableau/tasks/extract-refreshes/run/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/api/3.24/sites/{value.json.site_id}/tasks/extractRefreshes/{value.json.task_id}/runNow"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
        WITH BODY "{value.json.body}"
    ADD EVENT GetJob
        WITH SOURCE_TOPIC "analytics/tableau/jobs/get"
        WITH DESTINATION_TOPIC "analytics/tableau/jobs/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/api/3.24/sites/{value.json.site_id}/jobs/{value.json.job_id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "X-Tableau-Auth" "{value.json.auth_token}"
```

## MQTT examples

**SignIn** (`analytics/tableau/auth/signin`). The whole payload becomes the request body. Expect **200**:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "credentials": {
    "personalAccessTokenName": "coreflux-broker",
    "personalAccessTokenSecret": "<pat-secret>",
    "site": { "contentUrl": "PlantAnalytics" }
  }
}
```

Response:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "credentials": {
    "site": { "id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d", "contentUrl": "PlantAnalytics" },
    "user": { "id": "d5704762-603a-42b2-a9a7-f9cd2b2c4253" },
    "token": "HvZMqFFfQQmOM4L-AZNIQA|5fI6T54OPK1Gn1p4w0RtHv6EkojWRTwq|9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
    "estimatedTimeToExpiration": "364:23:59"
  }
}
```

On Tableau Server, `"contentUrl": ""` signs in to the Default site. Tableau Cloud always needs the site's `contentUrl`.

**RunExtractRefreshTask** (`analytics/tableau/tasks/extract-refreshes/run`), expect **200** with a job:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "auth_token": "<token from SignIn>",
  "site_id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
  "task_id": "9f8e7d6c-5b4a-3f2e-1d0c-9b8a7f6e5d4c",
  "body": {}
}
```

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{ "job": { "id": "7b6b59a8-ac3c-4d1d-2e9e-0b5b4ba8a7b6", "mode": "Asynchronous", "type": "RefreshExtract" } }
```

Get `task_id` from **ListExtractRefreshTasks** (`analytics/tableau/tasks/extract-refreshes/list`, payload `{"auth_token": "...", "site_id": "..."}`). A second request while the refresh is still queued returns **409** (`409093`).

**RefreshDataSource** (`analytics/tableau/datasources/refresh`) and **RefreshWorkbook** (`analytics/tableau/workbooks/refresh`) refresh an extract without a schedule. Expect **202** with a job:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{ "auth_token": "<token>", "site_id": "<site LUID>", "datasource_id": "<data source LUID>", "body": {} }
```

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{ "auth_token": "<token>", "site_id": "<site LUID>", "workbook_id": "<workbook LUID>", "body": {} }
```

**GetJob** (`analytics/tableau/jobs/get`): `{"auth_token": "<token>", "site_id": "<site LUID>", "job_id": "7b6b59a8-ac3c-4d1d-2e9e-0b5b4ba8a7b6"}`. Poll until `completedAt` is set, then check `finishCode`. Only site and server administrators can query jobs.

**GetViewData** (`analytics/tableau/views/data/get`), expect **200** `text/csv`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{ "auth_token": "<token>", "site_id": "<site LUID>", "view_id": "2474164d-8d37-4a4c-abc7-c2070fd25fd5", "max_age": "5" }
```

```text theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
Line,Shift,OEE,Good Parts
A,1,0.82,1200
B,1,0.91,1340
```

**GetViewDataFiltered** (`analytics/tableau/views/data/filter`) adds one view filter, `vf_<filter_field>=<filter_value>`. URL-encode both values (a field called `Plant Line` becomes `Plant%20Line`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{ "auth_token": "<token>", "site_id": "<site LUID>", "view_id": "<view LUID>", "max_age": "5", "filter_field": "Line", "filter_value": "B" }
```

**ListWorkbooks**, **ListViews** and **ListDataSources** (`analytics/tableau/workbooks/list`, `.../views/list`, `.../datasources/list`): `{"auth_token": "<token>", "site_id": "<site LUID>", "page_size": "100", "page_number": "1"}`. `page_size` is 1 to 1000.

**ServerInfo** (`analytics/tableau/server/info`): any payload, for example `{}`. **SignOut** (`analytics/tableau/auth/signout`): `{"auth_token": "<token>"}`, expect **204** with an empty body.

Control fields (`auth_token`, `site_id`, ids, paging) only fill the URL and headers. Only `body` (and the full payload for SignIn) is sent as the request body.

## Keeping the session

Each request needs the token and site LUID from the latest sign-in. A small action can store them when the sign-in reply arrives:

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION TableauKeepSession
ON TOPIC "analytics/tableau/auth/signin/response" DO
    SET "token" WITH GET JSON "credentials.token" IN PAYLOAD AS STRING
    SET "site" WITH GET JSON "credentials.site.id" IN PAYLOAD AS STRING
    KEEP TOPIC "analytics/tableau/session/auth_token" WITH {token}
    KEEP TOPIC "analytics/tableau/session/site_id" WITH {site}
```

Sign in again before the session expires (for example every 100 minutes on Tableau Cloud), or when a call returns **401** with code `401002`. Restrict who can read the `analytics/tableau/#` topics with a rule, because sign-in payloads carry the PAT secret and replies carry the session token.

## Webhooks (companion, not packaged)

Tableau [webhooks](https://help.tableau.com/current/developer/webhooks/en-us/) can `POST` to an HTTPS URL when, for example, a data source refresh succeeds or fails (`DatasourceRefreshSucceeded`, `DatasourceRefreshFailed`, `WorkbookRefreshFailed`). Receiving them needs a separate `ENABLE_SERVER` REST API connector on a publicly reachable HTTPS address. That companion connector is not part of this template. Writing `.hyper` extract files uses the Hyper API client library, not REST, so it is not covered either.

## Notes

* View data is summary-level, and a dashboard returns only its first sheet. Data is cached for 60 minutes by default. Lower `max_age` (minimum 1) when you need fresher numbers.
* PATs expire after 15 days without use (and at the site's maximum PAT lifetime). Sign in at least that often or rotate the PAT.
* Tableau Cloud allows 20 concurrent long-running requests (view data, exports). Above that it returns **429**; retry later.
* Non-admin users can run refresh tasks and refresh data sources or workbooks they own. Querying jobs needs a site or server administrator. RunNow fails if an administrator has disabled it for the site.
* Before API 3.28, RefreshDataSource always runs a full refresh. Incremental refresh bodies need `/api/3.28`.
* Without `Accept: application/json`, Tableau answers in XML. The template sets JSON for every call except view data, which is CSV.
* Not covered: publishing workbooks or data sources (multipart uploads), view image, PDF and crosstab exports, the Metadata API (GraphQL), and connected-app JWT or unified access token sign-in. JWT sign-in works through the same SignIn event with a different `credentials` body.
* Suggested branches: `feat/rest-api-route-tableau`, `feat/docs-rest-api-tableau` (Hugo pushes).

## Related

* [REST API Connectors](/latest/lot-language/routes/rest-api-routes)
* [Tableau REST API reference](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm)
* [Authentication methods](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_authentication.htm)
* [Jobs, tasks and schedules](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm)
* [Workbooks and views](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm)
* [REST API versions](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_versions.htm)
* [Tableau REST API Postman collection](https://github.com/tableau/tableau-postman)


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