tableau-cloud-audit
Your entire Tableau Cloud estate in one DuckDB file.
Tableau Cloud tells you what happened yesterday. It won't tell you who hasn't signed in since March, which workbooks nobody has opened all year, or who can actually see the HR dashboard — and Admin Insights forgets everything after 90 days. This collector answers those questions with SQL, from a file you own.
An open-source, client-executed collector that reads a Tableau Cloud site through its official APIs (REST + Admin Insights via the VizQL Data Service) and lands everything in a single local DuckDB file — so you can understand optimization opportunities on economics (license waste), privacy & security (who can see what), and governance (zombie content, ownerless assets).
Status: working alpha. Collects users, groups, memberships, projects, workbooks, views, data sources, connections, permission rules (incl. project default templates) and the Admin Insights activity sources (TS Events, TS Users, Site Content, Tokens, Job Performance). Interfaces and schema may still change.
Design principles
- You run it, on your machine, with your revocable PAT. No credentials shared, no remote access, no telemetry. When the run is done, revoke the token.
- Privacy by construction, not by discipline. User identities are pseudonymised
(
U-####) before anything is written — enforced by a mandatory scrub gate on the write path, not by convention (details below). - No hidden logic. The collector extracts and stores — it computes nothing about
you. Every endpoint and field it reads is documented in
docs/api-coverage.md,
and the complete definition of "what counts as personal data" lives in one reviewable
file:
src/tca/pseudo/manifest.py. - One file, growing over time. Run it monthly: events are accumulated and deduplicated, so your history outlives Tableau's 90-day Admin Insights retention — and coverage gaps between runs are detected, not hidden.
Quickstart
pip install tableau-cloud-audit
tca init # wizard → collector.toml + package file
export TCA_PAT_SECRET='<PAT secret>' # never stored in files
export TCA_DB_KEY='<passphrase>' # optional: encrypt the file at rest
tca verify # PAT, site, Admin Insights/VDS access
tca collect # collect everything (resumable)
Then explore:
tca summary # what's in the file: runs, row counts, event history, gaps
tca peek users # browse the latest snapshot WITH real names (local only)
tca resolve U-0042 # one pseudonym → identity (local vault only)
tca export # the shareable copy: everything EXCEPT the identity vault
Interrupted run (crash, Ctrl-C)? Nothing is lost: tca collect --resume continues
where it stopped, skipping already-collected pages — API calls included.
What you can answer
Once the file exists it's just DuckDB — no API, no rate limits, no waiting. The first pass at license waste, seats paying for nobody:
SELECT user_pseudo, site_role, last_login_at,
date_diff('day', last_login_at, current_timestamp::TIMESTAMP) AS days_inactive
FROM state.v_users_current
WHERE last_login_at IS NULL
OR last_login_at < current_timestamp::TIMESTAMP - INTERVAL 90 DAY
ORDER BY days_inactive DESC NULLS FIRST;
┌─────────────┬───────────┬─────────────────────┬───────────────┐
│ user_pseudo │ site_role │ last_login_at │ days_inactive │
├─────────────┼───────────┼─────────────────────┼───────────────┤
│ U-0117 │ Creator │ NULL │ NULL │
│ U-0042 │ Creator │ 2025-09-14 08:21:03 │ 320 │
│ U-0288 │ Explorer │ 2026-01-27 16:44:51 │ 185 │
│ U-0031 │ Creator │ 2026-03-02 09:05:12 │ 151 │
└─────────────┴───────────┴─────────────────────┴───────────────┘
Swap state.v_users_current for clear.users and the same query comes back with real
names — that join happens locally, against a vault the shared export doesn't contain.
Six more validated recipes — broad "All Users" grants, content nobody opens, ownerless assets, embedded credentials — in the query cookbook.
The DuckDB package file
Everything lives in one DuckDB database with five schemas:
| Schema | Contents | PII |
|---|---|---|
meta |
Bookkeeping: file identity, one row per collection run, schema migration registry, event-coverage windows with gap detection | none |
raw |
The source of truth: one row per API response page, payload stored as JSON, already pseudonymised | none — user identities appear only as U-#### |
state |
Queryable typed tables (1 row = 1 object: users, groups, projects, content, views, connections, permission rules), rebuilt mechanically from raw on every run; v_*_current views show the latest snapshot |
none |
history |
Append-only accumulators deduplicated on natural keys (history.events): run monthly and the event log outlives the 90-day retention |
none |
identity |
A single table (identity.map): the pseudonym ↔ real identity mapping. The only place real identities exist. |
yes — by design, isolated here |
The schema is versioned: opening a file applies pending additive-only migrations automatically, and files written by a newer collector are refused rather than mangled. Tables and columns never disappear across versions — the guarantee is enforced by a schema contract test in CI. Full table-by-table guide: docs/package-file-schema.md.
Set TCA_DB_KEY to encrypt the whole file at rest (DuckDB native AES encryption). If
unset, the collector warns and writes an unencrypted file.
How pseudonymisation works
Four mechanisms, all declared in the PII manifest and enforced by a scrub gate that every payload must pass before it can be written:
- User objects → stable pseudonyms. Tableau user objects (in
/users, group memberships, content owners, permission grantees) have their identity fields replaced by a stableU-####; the real identity goes into the local vault. Same person = same pseudonym, forever, across runs. - Credential redaction. Database usernames in connections carry no Tableau
identity to map — they become
[redacted], keeping the presence signal. - Query-time minimization. For Admin Insights sources the collector chooses the
columns: identity columns it doesn't need (
Actor User Name,Owner Emailin Site Content, device names, error messages…) are never even requested. - E-mail reverse lookup. Sources that identify owners only by e-mail (Tokens,
Job Performance) are resolved against the vault: known e-mail →
U-####, unknown →[redacted]. An e-mail never survives in the file.
A blocking safety net backs all of this: after scrubbing, any e-mail-shaped string or known user LUID still present anywhere in the payload aborts the write. A gap in the manifest fails loudly instead of leaking silently.
And your side of the file stays readable: tca peek (or the clear.* SQL views)
re-joins pseudonyms with the vault at read time, so you browse your own data with
real names. The export physically lacks the vault, so on the shared copy that
recombination is impossible — not merely forbidden.
Sharing data with an analyst
tca export produces the only artifact meant to leave your machine: a plain,
unencrypted DuckDB copy with every schema except identity, verified (no
e-mail-shaped strings anywhere) and accompanied by a SHA-256 checksum. Open it with
any DuckDB client to inspect exactly what would be shared, before sharing it —
convenience views included:
More recipes like this in the query cookbook.
Have a question? Ask an AI
Got a doubt about how this project works, or how to use or extend it? Hand
AI_GUIDE.md to Claude (or any capable AI assistant) and then
ask away — it briefs the assistant on the project's purpose, architecture,
privacy model, and conventions so you get grounded answers instead of guesses.
Requirements
- Python ≥ 3.11
- A Tableau Cloud site and a Personal Access Token of a site administrator
- For activity data: the Admin Insights project provisioned (open it once as an
admin) —
tca verifychecks this for you
License
Apache-2.0.
Tableau and Tableau Cloud are trademarks of Salesforce, Inc. This project is an independent open-source tool and is not affiliated with or endorsed by Salesforce.
Metadata
Release files for tableau-cloud-audit 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tableau_cloud_audit-0.2.0.tar.gz | 1.5 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tableau_cloud_audit-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.5 MB
Release files / tableau_cloud_audit-0.2.0.tar.gz
| Download URL | tableau_cloud_audit-0.2.0.tar.gz |
|---|---|
| Size | 1.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5124d0954fe6e7f64783d8fb56d425f29e2fa8b07b422f9abd40eb66c0c1f978
|
|
BLAKE2b-256 checksum How to use checksums |
4642ed0b18e1fef3e4ad1033688912bb0e1116d440caeb6e1aecb1dcdf4440e9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency logRelease files / tableau_cloud_audit-0.2.0-py3-none-any.whl
| Download URL | tableau_cloud_audit-0.2.0-py3-none-any.whl |
|---|---|
| Size | 76.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f58b7e5eca97e55c8d30daa82b5a4ce0b8b0a506e8b1a0fdcc82b33a86ef71d8
|
|
BLAKE2b-256 checksum How to use checksums |
78185194d8d0c8bf4f54e00f16e00c29327d01ecbcabca40a3d3536f1273b3e3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency log