Tableau REST API

The Tableau REST API with a Connected App (JWT)

Authenticate to the Tableau REST API with a Connected App JWT instead of a password or PAT, and the endpoints you can reach once you do.

Eric SummersUpdated 2026-09-19markdown

The Tableau REST API is how you automate Tableau Server and Tableau Cloud from the outside: publish and download workbooks, refresh extracts, pull view data and images, manage users, and read permissions. This page is the practical map: how to sign in with a Connected App so you never store a password, and the endpoints you can call once you hold a session token.

A Connected App with direct trust lets your service mint a short-lived JSON Web Token (JWT), sign it with a secret, and exchange it for a session. No stored password, no personal access token to rotate by hand, and you can scope exactly what the token is allowed to do.

What you need first

Create the Connected App in Tableau (Settings, then Connected Apps, then "Direct Trust"). It gives you three values:

Value Where it goes
Client ID (the Connected App id) the JWT iss claim
Secret ID the JWT header kid
Secret value the HMAC key you sign the JWT with (store it like a password)

The Connected App must be enabled, and the user you sign in as (sub) must already exist on the site with the rights you expect. Direct trust for the REST API needs Tableau Cloud, or Tableau Server 2022.1 or later.

Step 1: mint the JWT

Sign an HS256 JWT with the secret value. The header carries the secret id and the client id; the payload carries the user to act as, the audience, an expiry, a unique id, and the scopes.

// header
{ "alg": "HS256", "typ": "JWT", "kid": "<SECRET_ID>", "iss": "<CLIENT_ID>" }

// payload
{
  "iss": "<CLIENT_ID>",
  "sub": "user@example.com",        // the Tableau user to act as
  "aud": "tableau",
  "exp": <now + 5 minutes, epoch>,   // keep it short; Tableau rejects long-lived tokens
  "jti": "<a fresh UUID per token>",
  "scp": ["tableau:content:read", "tableau:views:download"]
}

Rules that trip people up:

Step 2: exchange the JWT for a session

Post the JWT to the sign-in endpoint. The site is identified by its contentUrl (the slug in the site URL; the empty string is the default site on Server).

POST https://<your-server>/api/<version>/auth/signin
Content-Type: application/json

{ "credentials": { "jwt": "<JWT>", "site": { "contentUrl": "<site-content-url>" } } }

The response carries three things you keep for the rest of the session: the credentials token, the site id, and the user id. Send the token on every later call as the header X-Tableau-Auth: <token>. Sessions expire (commonly after a period of inactivity), so sign in again when a call returns 401. Sign out with POST /api/<version>/auth/signout when you are done.

Set <version> to the REST API version your Tableau supports (for example 3.21). Add Accept: application/json if you want JSON back; the API speaks XML by default.

The endpoint sheet

Almost every resource lives under /api/<version>/sites/<site-id>/..., using the site id from sign-in. This is the working subset, grouped by what you are trying to do. Each row lists the scope area you will typically need in scp.

Auth and site

Method Path Scope area Purpose
POST /auth/signin (the JWT itself) Exchange the JWT for a session token
POST /auth/signout session End the session
GET /sites/<site-id> content:read Read the current site's settings
GET /sites/<site-id>/projects content:read List projects (the folders content lives in)

Workbooks

Method Path Scope area Purpose
GET /sites/<site-id>/workbooks content:read List workbooks (filter by project, owner, name)
GET /sites/<site-id>/workbooks/<id> content:read One workbook's metadata and its views
GET /sites/<site-id>/workbooks/<id>/content workbooks:download Download the .twbx
POST /sites/<site-id>/workbooks content (publish) Publish a workbook (multipart upload)
PUT /sites/<site-id>/workbooks/<id> content Update a workbook (project, owner, tags)
DELETE /sites/<site-id>/workbooks/<id> content Delete a workbook
POST /sites/<site-id>/workbooks/<id>/refresh tasks:run Trigger an extract refresh now

Views

Method Path Scope area Purpose
GET /sites/<site-id>/views content:read List views across the site
GET /sites/<site-id>/views/<id>/data views:download The view's summary data as CSV
GET /sites/<site-id>/views/<id>/image views:download Render the view to PNG (supports ?resolution=high)
GET /sites/<site-id>/views/<id>/pdf views:download Render the view to PDF
GET /sites/<site-id>/views/<id>/crosstab/excel views:download The view as an Excel crosstab

Filter a rendered view by adding ?vf_<Field Name>=<value> to the image, pdf, or data call, the same way a URL parameter filters an embedded view.

Data sources

Method Path Scope area Purpose
GET /sites/<site-id>/datasources content:read List published data sources
GET /sites/<site-id>/datasources/<id>/content datasources:download Download the .tdsx
POST /sites/<site-id>/datasources content (publish) Publish a data source
POST /sites/<site-id>/datasources/<id>/refresh tasks:run Refresh its extract now

Jobs, schedules, and tasks

Method Path Scope area Purpose
GET /sites/<site-id>/jobs tasks List background jobs (refreshes, subscriptions)
GET /sites/<site-id>/jobs/<id> tasks Poll one job's status (queued, running, success, failed)
GET /sites/<site-id>/tasks/extractRefreshes tasks List scheduled extract-refresh tasks
POST /sites/<site-id>/tasks/extractRefreshes/<id>/runNow tasks:run Run a scheduled refresh task now

An extract refresh returns a job id. Poll GET /jobs/<id> until it finishes rather than assuming it is done.

Users, groups, and permissions

Method Path Scope area Purpose
GET /sites/<site-id>/users users:read List users on the site
POST /sites/<site-id>/users users Add a user to the site
GET /sites/<site-id>/groups groups:read List groups
GET /sites/<site-id>/workbooks/<id>/permissions permissions Read a workbook's permission rules

Metadata (GraphQL)

For "where is this field used" and lineage questions, the Metadata API is a single GraphQL endpoint rather than REST:

Method Path Purpose
POST /api/metadata/graphql Query lineage: which workbooks use a data source, which fields a sheet reads, downstream impact

The same X-Tableau-Auth token authorizes it.

A worked run

Sign in, list workbooks, and save a view as a PNG:

# 1. mint the JWT (in your language of choice), then:
TOKEN=$(curl -s -X POST https://SERVER/api/3.21/auth/signin \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"credentials":{"jwt":"'"$JWT"'","site":{"contentUrl":"SITE"}}}' \
  | jq -r .credentials.token)
SITE=$(...)   # site id from the same response

# 2. list workbooks
curl -s -H "X-Tableau-Auth: $TOKEN" -H "Accept: application/json" \
  https://SERVER/api/3.21/sites/$SITE/workbooks

# 3. render a view to PNG
curl -s -H "X-Tableau-Auth: $TOKEN" \
  https://SERVER/api/3.21/sites/$SITE/views/VIEW_ID/image?resolution=high \
  -o view.png

Doing it the TableauOps way

TableauOps Scout already speaks the REST API for you: it signs in with a Connected App, watches extract refreshes and health, and surfaces failures, so you do not hand-roll the polling. If you want an AI agent to drive Tableau, the MCP connector is the same idea one level up: connect it and the agent calls these operations through a tool, with the auth handled for it.

FAQ

Why use a Connected App instead of a personal access token?

A Connected App JWT is minted fresh, expires in minutes, and carries only the scopes you grant, so a leaked token is low-value and short-lived. A PAT is a long-lived secret tied to one user that you have to store and rotate. For a service, the Connected App is the safer default.

My JWT sign-in returns 401. What is wrong?

Work through the usual four: the Connected App is disabled, the exp is too far out (or your clock is skewed), the jti was reused, or the sub user does not exist on that site. If sign-in works but a later call 403s, the token is missing that call's scope in scp.

Which API version should I put in the path?

Match it to your Tableau version. A newer server accepts older versions, so pin a version your code was tested against (for example 3.21) rather than tracking the latest.

Can I act as different users with one Connected App?

Yes. The sub claim names the user the session acts as, so one Connected App can mint a token for any user on the site, subject to that user's own permissions. That is what makes it useful for a service that automates on behalf of many people.