Data assets
A data asset is one table, one view, or one parquet dataset that you want Measured Cloud to look after. Every asset belongs to one data source, and it is the home of everything you check about that table: its profile, its rules, and the validations that run them.
The Data Assets page lists every asset in your workspace, grouped by data source, with a Health (30d) column (the share of the asset's rule checks that passed over the last 30 days, see Health score) and a Validations column showing which validations run each one. Click a row to open the asset. Its page leads with an AI summary of the table, then Table Health (the latest profile, column by column), then the Rules card where you author checks. The header carries Run (run this asset's rules now), Profile, Edit, History, and Delete.
Data model
DataAsset lives in the per-tenant schema.
| Column | What it stores |
|---|---|
name | The qualified name as it appears in the source (analytics.orders, s3://bucket/orders/). |
type | table / view / query / parquet_dataset. |
datasource_id | FK to the parent Datasource. |
config | Open-ended JSONB. For SQL assets this is the schema + table; for object-store assets, the prefix and partitioning hint. |
description | Free-text blurb. Rendered under the asset name in the list view. Nullable. |
owner_email | Free-text email of the human who owns the asset. Not FK-bound — owners can be people outside the org. Nullable. |
tags | JSONB list of short strings (e.g. ["pii", "revenue"]). |
is_restricted | Boolean governance flag for sensitive (PII / PHI / PCI) assets, default false. When true, the asset is invisible to restricted viewers — see Restricted assets below. |
Restricted assets
Mark an asset is_restricted: true when it holds sensitive data (PII, PHI, PCI) that not every read-only user should even know exists. The flag drives a server-side visibility filter keyed on the viewer's membership:
- Every viewer-role membership row (
workspace_memberships,team_memberships, and legacy org-level viewer rows) carries aviewer_access_levelofunrestricted(the default) orrestricted. Only a viewer row carries one: changing a viewer to another role clears it, and making someone a viewer starts themunrestricted. - A viewer whose effective access to a workspace is restricted does not see
is_restrictedassets there — they vanish from lists and search, and fetching one by id returns a plain 404 (indistinguishable from a nonexistent id). On the asset endpoints the flag therefore never leaks the asset's existence; for data derived from the asset, see the caveat below. - Everyone else — editors, workspace admins, super admins, and unrestricted viewers — sees restricted assets exactly like any other asset. A legacy viewer row with a
NULLaccess level reads asunrestricted.
The filter is enforced at the database-session layer (the same listener that enforces workspace and team isolation — see Multi-tenancy), so every endpoint that returns assets respects it without per-route code.
Semantics worth knowing:
- Scoping is per workspace. A user who is a restricted viewer in workspace A but an editor in workspace B keeps full visibility of B's restricted assets; only A's are hidden.
- Org-wide assets fail closed. An asset with no workspace (
workspace_id IS NULL) is visible in every workspace's view, so if a user is a restricted viewer anywhere, org-wide restricted assets are hidden from them everywhere. - Changing the flag is workspace-admin-or-higher. Editors can create and edit assets but cannot set or clear
is_restricted(the API returns 403) — both directions are a data-governance decision: clearing it widens visibility for every restricted viewer, setting it hides the asset from them. - A restricted asset's results hide with it. Validation results name their asset, so the session-level filter hides every result about a restricted asset from a restricted viewer — on the results list, a result's detail (a plain 404), execution runs and exports alike. A run's summary row, which names no single asset, stays visible. Profile runs, anomalies, AI suggestions and root-cause analysis are gated by parent-asset visibility on their endpoints: a restricted viewer gets the same empty list / 404 a nonexistent id would produce.
Restrict a viewer
A workspace admin (or a super admin) decides which viewers are restricted — the same people who may flag an asset:
- Open Settings → Members for the workspace.
- On a viewer's row, set Access to Restricted (or back to Unrestricted). If you signed in with a password you may be asked to confirm it first. The control is disabled, with a "Requires …" hint, for anyone else, and non-viewer rows show "—".
- For someone who is a viewer only through a team, a super admin sets it on the member row under Settings → Teams instead.
The change takes effect on the viewer's next request and is recorded in the audit log (workspace_membership.viewer_access_updated / team.member_viewer_access_updated, with the old and new level). Through the API: PUT /api/v1/organizations/{org_id}/workspaces/{workspace_id}/members/{user_id}/viewer-access with {"viewer_access_level": "restricted"} (a non-viewer member is a 400), or PATCH /api/v1/organizations/{org_id}/teams/{team_id}/members/{user_id} with a viewer_access_level.
PATCH /api/v1/.../data-assets/{id} with {"data": {"is_restricted": true}} flips the flag (subject to the role gate above).
Owning team
An asset can have an owning team (team_id), and its rules always carry it. With no team the asset reads Workspace-shared. The team is a label: the asset stays visible to the whole workspace unless a workspace admin turns on Restrict to team (team_restricted). Then only that team (and admins) can see the asset, its rules, profile runs, anomalies and suggestions. That is a different switch from Mark as restricted above, which hides PII from restricted viewers. Set both from the asset's Edit dialog, or PATCH them. See Team ownership for who can change what and the "mixing two teams' assets" rule.
Tags, owners, descriptions
Each asset carries three optional annotations on top of the qualified name. They exist so a workspace with hundreds of assets stays navigable.
- Description — a short human-readable blurb. Shows up under the asset name in the list view and is included in the asset detail.
- Owner — a free-text email. The list view renders the email as-is; there is no autocomplete against
public.usersbecause an asset's owner is often a person outside the org (a customer DBA, a vendor on-call). - Tags — a JSONB list of short strings. Lower-case, free-form (
pii,revenue,marketing-export). The UI editor commits a tag on Enter or comma; backspace on an empty buffer pops the last chip.
Editing
PATCH /api/v1/.../data-assets/{id} with a partial body — only the fields you want to change need to be present.
PATCH /api/v1/organizations/{org}/workspaces/{ws}/data-assets/{id}
Content-Type: application/json
{"data": {"tags": ["pii", "revenue"], "owner_email": "data-team@example.com"}}
Sending "tags": [] empties the tag list. Sending "description": null clears the description.
Filtering by tag
On the Data Assets page, every column sorts and filters from its own header: Name, Source, Type, Team, Tags (contains any of the asset's tags), and Last profiled (by the latest profile run's status: never profiled, running, completed or failed). Clicking a tag chip on a row sets the Tags filter. Active filters show as removable chips above the table, and a source group with no matching asset is hidden while a filter is on. The Columns menu beside them shows or hides columns. Every table in the app has the same Columns menu: tick the columns you want, or Reset columns to show them all again. The first column (the row's name) and the row actions always stay. Your choice is remembered per table in this browser.
List endpoints accept ?tag=<value>:
GET /api/v1/organizations/{org}/workspaces/{ws}/data-assets?tag=pii
The server returns assets whose tags JSONB array contains the supplied tag string (exact match, case-sensitive — tags are normalised to lower-case at write time by the UI). Combining ?tag= with ?name= is supported; both filters AND together.
Operations you can do
- Add tables from their data source — much faster than typing names. Open the source's Manage source window (click the source's name on Data Assets). Its Tables section lists tables and views and filters out system schemas (
pg_catalog,information_schema, …). Tick the ones you want and Add N tables registers them in one step; re-adding a table that already exists is a no-op. See Add tables from a source. - Create manually by typing the qualified name.
- Profile the asset to compute column statistics (row count, null %, distinct count, min / max / mean for numerics). See Profile runs.
- Author rules on the asset's Rules card — by picking a rule type, describing it in plain English, applying a template, or accepting AI suggestions. See Rules.
- Run the asset's rules on demand with the ▶ button in the header, or pick its rules into a validation to run them on a schedule alongside related tables.
- See which validations run it — the Rules card names every validation that holds at least one of the asset's rules, each linking to the validation. Each rule's Validations column goes further: a chip per validation that runs that rule, with its last run's status and time, opening the validation's Run history (see Rules → Monitor a rule's validations).
- View the change history of the asset and its rules with History, backed by
GET /api/v1/organizations/{org_id}/audit-events?resource_type=data_asset&resource_id={asset_id}.
Health score
An asset's health score is the share of its rule checks that passed over every run that checked it in the last 30 days: validation runs, the asset's own Run, and single-rule runs alike. The Data Assets list shows it in the Health (30d) column (the band plus "88% · 12% failed"). The asset page leads its stat strip with a Health (30d) card: the pass rate, the failure rate ("12% of checks failed in the last 30 days"), and the counts behind them. An asset with no runs in the window reads No data / "No runs in 30 days".
The numbers come from GET /api/v2/.../health/data-assets (every asset with a run in the window, in one read) and GET /api/v2/.../data-assets/{asset_id}/health, both with an optional ?window_days= (1–365). A restricted asset's health is hidden from restricted viewers, like the asset itself. See Health score for the full definition.
Deleting an asset
Deleting an asset moves it to the recycle bin rather than destroying it. The asset and its rules are soft-deleted together under one deletion group and the request returns 204; they disappear from every list, search, and query but stay recoverable for RECYCLE_BIN_RETENTION_DAYS (30 by default), after which a daily cron purges them.
An asset whose rules are in a validation deletes just the same — the validation is a shared object and is never touched. It simply stops running the binned asset's rules while it's in the bin, and re-includes them automatically when you restore the asset.
Before anything moves, the Delete confirmation (on the Data Assets list and the asset page alike) lists what goes to the bin with the asset (its rules, profile runs, and so on), says that execution runs and their results are kept (they stay on the Results page, even after a purge), and says how long the asset can be restored.
Restore the asset (its rules come back with it) or purge it early from the recycle bin — see Restore or permanently delete from the recycle bin. Deleting an asset needs a workspace admin or super admin; restore is editor-tier and a permanent purge needs a workspace admin or super admin — see the Recycle bin RBAC matrix. When an asset is finally purged (early or by the retention sweep), its owned subtree — rules, profiling history, anomaly events, AI suggestions — goes with it, while the immutable result history is preserved.