Governance API
The Governance module provides fine-grained access control and compliance features including role-based access control (RBAC), user groups, destination policies, access policies, and deletion rules.
Endpoint Groups
| Group | Description |
|---|---|
| Roles | System and custom role management |
| Permissions | Granular permission definitions |
| Groups | User group management |
| Members | Group membership management |
| Destination Policies | Sync-time governance policies |
| Access Policies | Data row-level access restrictions |
| Deletion Rules | Audience-scoped warehouse deletion with audit logs |
Roles
Roles define sets of permissions that can be assigned to workspace members or groups. Zeotap includes three built-in system roles (owner, admin, member) and supports custom roles.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/workspaces/{id}/roles | List all roles |
POST | /api/v1/workspaces/{id}/roles | Create a custom role |
GET | /api/v1/workspaces/{id}/roles/{roleId} | Get a role with permissions |
PUT | /api/v1/workspaces/{id}/roles/{roleId} | Update a custom role |
DELETE | /api/v1/workspaces/{id}/roles/{roleId} | Delete a custom role |
List Roles
GET /api/v1/workspaces/{id}/roles
[
{
"id": "00000000-0000-0000-0000-000000000001",
"workspace_id": null,
"name": "owner",
"description": "Full access to all workspace resources",
"is_system": true,
"permissions": ["sources.read", "sources.write", "sources.delete", "..."],
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"workspace_id": "660e8400-e29b-41d4-a716-446655440000",
"name": "data_analyst",
"description": "Read-only access to models and audiences",
"is_system": false,
"permissions": ["sources.read", "models.read", "audiences.read"],
"created_at": "2024-01-15T09:30:00Z",
"updated_at": "2024-01-15T09:30:00Z"
}
]Create Custom Role
POST /api/v1/workspaces/{id}/roles
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Role name (must be unique within workspace) |
description | string | No | Description of the role’s purpose |
permissions | array of strings | Yes | Permission keys to grant |
{
"name": "data_analyst",
"description": "Read-only access to models and audiences",
"permissions": [
"sources.read",
"models.read",
"audiences.read",
"traits.read",
"insights.read"
]
}System Roles
| Role | ID | Description |
|---|---|---|
owner | 00000000-0000-0000-0000-000000000001 | Full access; can manage billing and workspace settings |
admin | 00000000-0000-0000-0000-000000000002 | Full access except billing and ownership transfer |
member | 00000000-0000-0000-0000-000000000003 | Standard access; cannot manage workspace settings |
Permissions
Permissions are granular access control strings organized by category.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/permissions | List all available permissions (global catalog) |
GET | /api/v1/workspaces/{id}/permissions/me | Get the caller’s permissions in this workspace |
Response
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"key": "sources.read",
"category": "sources",
"description": "View sources and connection details",
"created_at": "2024-01-01T00:00:00Z"
},
{
"id": "660e8400-e29b-41d4-a716-446655440000",
"key": "sources.write",
"category": "sources",
"description": "Create and update sources",
"created_at": "2024-01-01T00:00:00Z"
}
]Permission Categories
| Category | Permissions | Description |
|---|---|---|
sources | read, write, delete | Data warehouse connections |
models | read, write, delete | SQL models |
destinations | read, write, delete | Write targets |
syncs | read, write, delete, trigger | Reverse ETL pipelines |
audiences | read, write, delete | Audiences |
traits | read, write, delete | Computed attributes |
identity_graphs | read, write, delete, trigger | Identity resolution graphs |
journeys | read, write, delete, activate | Orchestration workflows |
events | read, write, delete | Streams (real-time event collection) |
loaders | read, write, delete, trigger | Data loaders |
governance | read, write | RBAC and compliance |
insights | read | Analytics and monitoring |
workspace | read, write, members | Workspace settings |
Groups
Groups organize workspace members and can be assigned a role that applies to all members.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/workspaces/{id}/groups | List all groups |
POST | /api/v1/workspaces/{id}/groups | Create a group |
GET | /api/v1/workspaces/{id}/groups/{groupId} | Get a group |
PUT | /api/v1/workspaces/{id}/groups/{groupId} | Update a group |
DELETE | /api/v1/workspaces/{id}/groups/{groupId} | Delete a group |
Create Group
POST /api/v1/workspaces/{id}/groups
{
"name": "Marketing Team",
"description": "Marketing department members",
"role_id": "550e8400-e29b-41d4-a716-446655440000"
}Group Object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique identifier |
workspace_id | string (UUID) | Owning workspace |
name | string | Group name |
description | string | Description |
role_id | string (UUID) or null | Role assigned to group members |
role_name | string | Name of the assigned role |
member_count | integer | Number of members in the group |
created_at | string (ISO 8601) | Creation timestamp |
updated_at | string (ISO 8601) | Last update timestamp |
Members
Manage group membership.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/workspaces/{id}/groups/{groupId}/members | List group members |
POST | /api/v1/workspaces/{id}/groups/{groupId}/members | Add a member |
DELETE | /api/v1/workspaces/{id}/groups/{groupId}/members/{accountId} | Remove a member |
Add Member
POST /api/v1/workspaces/{id}/groups/{groupId}/members
{
"account_id": "770e8400-e29b-41d4-a716-446655440000"
}Group Member Object
| Field | Type | Description |
|---|---|---|
group_id | string (UUID) | Group ID |
account_id | string (UUID) | Account ID |
email | string | Member email |
name | string | Member name |
avatar_url | string | Member avatar URL |
created_at | string (ISO 8601) | When the member was added |
Destination Policies
Destination policies are sync-time governance policies that restrict which records can be sent to specific destination types. They act as guardrails to prevent sensitive data from reaching certain platforms. The API path remains destination-rules for backwards compatibility.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/workspaces/{id}/destination-rules | List all destination policies |
POST | /api/v1/workspaces/{id}/destination-rules | Create a destination policy |
GET | /api/v1/workspaces/{id}/destination-rules/{ruleId} | Get a destination policy |
PUT | /api/v1/workspaces/{id}/destination-rules/{ruleId} | Update a destination policy |
DELETE | /api/v1/workspaces/{id}/destination-rules/{ruleId} | Delete a destination policy |
GET | /api/v1/workspaces/{id}/destination-types/{destType}/rules | List destination policies by destination type |
Create Destination Policy
POST /api/v1/workspaces/{id}/destination-rules
{
"name": "GDPR - No EU Customers to Facebook",
"description": "Prevent EU customer data from being synced to Facebook Ads",
"parent_model_id": "770e8400-e29b-41d4-a716-446655440000",
"destination_type": "facebook_ads",
"filter_tree": {
"type": "condition",
"condition_type": "property",
"column": "country",
"operator": "not_in",
"value": ["DE", "FR", "IT", "ES", "NL", "BE", "AT", "SE", "DK", "FI"]
},
"enabled": true
}Destination Policy Object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique identifier |
workspace_id | string (UUID) | Owning workspace |
parent_model_id | string (UUID) | Model the policy applies to |
destination_type | string | Destination type this policy restricts |
name | string | Display name |
description | string | Description |
filter_tree | object | Filter criteria (same format as audience filters) |
enabled | boolean | Whether the policy is active |
created_by | string (UUID) | Account that created the policy |
created_at | string (ISO 8601) | Creation timestamp |
updated_at | string (ISO 8601) | Last update timestamp |
Access Policies
Access Policies provide row-level access control by restricting which records a user or group can see. They use filter trees (same as audiences) to define visibility boundaries. The API path remains subsets for backwards compatibility.
Access Policy Categories
Categories organize access policies into logical groups (e.g., Region, Brand, Business Unit).
| Method | Path | Description |
|---|---|---|
GET | /api/v1/workspaces/{id}/subset-categories | List categories |
GET | /api/v1/workspaces/{id}/subset-categories/{catId} | Get a category |
POST | /api/v1/workspaces/{id}/subset-categories | Create a category |
PUT | /api/v1/workspaces/{id}/subset-categories/{catId} | Update a category |
DELETE | /api/v1/workspaces/{id}/subset-categories/{catId} | Delete a category |
Access Policy Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/v1/workspaces/{id}/subsets | List all access policies |
GET | /api/v1/workspaces/{id}/subsets/me | List access policies that apply to the caller |
POST | /api/v1/workspaces/{id}/subsets | Create an access policy |
GET | /api/v1/workspaces/{id}/subsets/{subsetId} | Get an access policy |
PUT | /api/v1/workspaces/{id}/subsets/{subsetId} | Update an access policy |
DELETE | /api/v1/workspaces/{id}/subsets/{subsetId} | Delete an access policy |
Create Access Policy
{
"name": "US Region",
"description": "Only US customer records",
"category_id": "550e8400-e29b-41d4-a716-446655440000",
"parent_model_id": "770e8400-e29b-41d4-a716-446655440000",
"filter_tree": {
"type": "condition",
"condition_type": "property",
"column": "country",
"operator": "equals",
"value": "US"
}
}Access Policy Assignments
Assign access policies to groups to restrict their data visibility. Assignments are nested under each access policy.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/workspaces/{id}/subsets/{subsetId}/assignments | List assignments for an access policy |
POST | /api/v1/workspaces/{id}/subsets/{subsetId}/assignments | Create an assignment |
DELETE | /api/v1/workspaces/{id}/subsets/{subsetId}/assignments/{assignmentId} | Remove an assignment |
GET | /api/v1/workspaces/{id}/groups/{groupId}/subsets | List access policies assigned to a group |
PUT | /api/v1/workspaces/{id}/groups/{groupId}/subsets | Replace the set of access policies assigned to a group |
Create Assignment
{
"group_id": "aae8400-e29b-41d4-a716-446655440000"
}Access Policy Object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique identifier |
category_id | string (UUID) | Parent category |
workspace_id | string (UUID) | Owning workspace |
parent_model_id | string (UUID) or null | Model the access policy applies to |
name | string | Display name |
description | string | Description |
filter_tree | object | Filter criteria |
created_by | string (UUID) | Account that created the access policy |
created_at | string (ISO 8601) | Creation timestamp |
updated_at | string (ISO 8601) | Last update timestamp |
Deletion Rules
Deletion rules erase warehouse data for an audience-scoped population, on demand or on a schedule, and record every run in an audit log. See Deletion Rules for the eligibility rules and safety controls.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/workspaces/{id}/deletion-rules | List all deletion rules |
POST | /api/v1/workspaces/{id}/deletion-rules | Create a deletion rule |
POST | /api/v1/workspaces/{id}/deletion-rules/impact | Resolve what else reads a candidate target list |
GET | /api/v1/workspaces/{id}/deletion-rules/{ruleId} | Get a deletion rule |
PUT | /api/v1/workspaces/{id}/deletion-rules/{ruleId} | Update a deletion rule |
DELETE | /api/v1/workspaces/{id}/deletion-rules/{ruleId} | Delete a deletion rule (its log is retained) |
POST | /api/v1/workspaces/{id}/deletion-rules/{ruleId}/dry-run | Start a non-destructive run |
POST | /api/v1/workspaces/{id}/deletion-rules/{ruleId}/run | Start a live run |
GET | /api/v1/workspaces/{id}/deletion-rules/{ruleId}/runs | List a rule’s runs |
GET | /api/v1/workspaces/{id}/deletion-runs | List the workspace deletion log |
GET | /api/v1/workspaces/{id}/deletion-runs/{runId} | Get one run with per-target detail |
Reads require deletion_rules.read, writes deletion_rules.write, and both run endpoints deletion_rules.execute.
Every endpoint additionally requires the workspace to have the Deletion Rules feature enabled by a platform administrator; otherwise they answer 403 deletion rules are not enabled for this workspace. See Availability.
Eligibility and safety refusals — an ineligible target, an empty filter, a column that cannot be cleared, a pinned join that has since drifted — answer 400 with the specific reason in error. Read it: it names the target and says what is wrong with it, which is the only place that information exists.
Create Deletion Rule
POST /api/v1/workspaces/{id}/deletion-rules
filter_tree is required and must carry at least one condition — a rule may not match every record by default. dry_run defaults to true and enabled to false, so omitting them creates an observe-only rule.
{
"parent_model_id": "770e8400-e29b-41d4-a716-446655440000",
"name": "Erase opted-out EU customers",
"description": "Fulfils standing right-to-erasure requests for EU opt-outs.",
"filter_tree": {
"type": "and",
"children": [
{
"type": "condition",
"condition_type": "property",
"field": "region",
"operator": "equals",
"value": "EU"
},
{
"type": "condition",
"condition_type": "property",
"field": "erasure_requested",
"operator": "equals",
"value": true
}
]
},
"targets": [
{ "model_id": "770e8400-e29b-41d4-a716-446655440000", "action": "delete_rows" },
{
"model_id": "881e8400-e29b-41d4-a716-446655440000",
"action": "null_columns",
"columns": ["shipping_address", "phone"]
}
],
"schedule": "0 3 * * *",
"dry_run": true,
"enabled": false
}Deletion Rule Object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique identifier |
workspace_id | string (UUID) | Owning workspace |
parent_model_id | string (UUID) | Model defining the entity the rule erases |
name | string | Display name (unique per workspace) |
description | string | Description |
filter_tree | object | Match criteria (same format as audience filters) |
targets | array | Target selections — see below |
schedule | string | Cron expression, optionally CRON_TZ=-prefixed. Empty means manual only |
enabled | boolean | Required for any live deletion |
dry_run | boolean | Observe mode: every run reports and mutates nothing |
last_run_at | string (ISO 8601) or null | When the most recent run started |
last_run_status | string | Status of the most recent run |
created_by | string (UUID) | Account that created the rule |
created_at | string (ISO 8601) | Creation timestamp |
updated_at | string (ISO 8601) | Last update timestamp |
Deletion Target Object
| Field | Type | Description |
|---|---|---|
model_id | string (UUID) | Target model. Must be the parent model or directly related to it, on the same warehouse, and backed by a warehouse table |
action | string | delete_rows (physical DELETE) or null_columns (UPDATE … = NULL) |
columns | array of string | Required for null_columns. May not include the join key to the parent model or the target’s primary key |
Trigger a Run
POST /api/v1/workspaces/{id}/deletion-rules/{ruleId}/run
POST /api/v1/workspaces/{id}/deletion-rules/{ruleId}/dry-run
Both return 202 Accepted with the opened run in running state — the deletion continues past the request, so poll the run log to follow it. /dry-run never mutates, whatever the rule’s own dry_run value. /run returns 400 when the rule is disabled.
Deletion Run Object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique identifier |
rule_id | string (UUID) or null | Source rule. null once the rule has been deleted |
workspace_id | string (UUID) | Owning workspace |
rule_name | string | Rule name snapshotted at run time |
rule_snapshot | object | Parent model, filter tree, targets and schedule as they were at run time |
trigger_type | string | manual or schedule |
mode | string | dry_run or execute |
status | string | running, completed, partial (some targets failed), or failed |
matched_count | integer | Records matched by the filter |
records_affected | integer | Records affected across all targets. In a dry run, records that would be |
error_message | string | Run-level failure detail |
initiated_by | string (UUID) or null | Account that started the run |
initiated_by_email | string | Email snapshotted at run time, so the log stays attributable |
started_at | string (ISO 8601) | Start timestamp |
completed_at | string (ISO 8601) or null | Completion timestamp |
targets | array | Per-target outcomes — see below |
Deletion Run Target Object
| Field | Type | Description |
|---|---|---|
model_name | string | Target model name at run time |
physical_table | string | Fully qualified warehouse table |
action | string | delete_rows or null_columns |
target_columns | array of string | Physical columns cleared |
records_affected | integer | Records affected. 0 on failure |
status | string | completed, failed, or skipped |
error_message | string | Failure detail for this target |
statement | string | The statement executed — or, in a dry run, the statement that would be |
executed_at | string (ISO 8601) | When this target was processed |