Destination Policies
Destination policies control which records may reach a given destination type. A policy is a filter attached to one of your models: when a sync sends that model to that kind of destination, only the records matching the filter are sent, and everything else is withheld before it leaves your warehouse.
How Destination Policies Work
A policy is scoped to two things — a parent model and a destination type — and carries a filter tree describing which records are permitted:
| Field | Description |
|---|---|
| Parent model | The model the policy governs. Only models configured as a parent entity can carry a policy. |
| Destination type | The kind of destination the policy applies to, such as facebook_ads or salesforce. It applies to every destination of that type, not to one connection. |
| Filter tree | The criteria a record must match to be sent. Same filter language as an audience. |
| Enabled | Whether the policy is enforced. A disabled policy is ignored entirely. |
Together, the model and the destination type are the policy’s identity: one enabled policy governs each model-and-destination-type pair. Creating a second policy for the same pair does not stack another restriction on top — express everything you need for that pair in a single filter tree, combining criteria with and / or groups.
When a sync runs, Zeotap looks for an enabled policy matching that sync’s model and its destination’s type. If it finds one, the policy’s filter is applied to the model’s query before any data is read, so non-matching records are never fetched, never mapped, and never sent. The sync itself still runs and succeeds — it simply carries fewer records.
Because the filter runs inside your warehouse as part of the model query, it can use everything an audience filter can: column values, conditions across related models, computed attributes, and membership of another audience.
A policy filters records, it does not block a sync, alter column values, or throttle how often a sync runs. Those are separate features — see Related Governance Controls below for which one covers each case.
What a Policy Applies To
| Scope | Behavior |
|---|---|
| Model | Only the parent model named on the policy. Syncs from other models are unaffected, even to the same destination. |
| Destination type | Every destination of that type in the workspace. You cannot scope a policy to a single destination connection, to a category such as “advertising”, or to all destinations at once. |
| Sync kind | Reverse ETL syncs and audience syncs alike, since both read through the model. |
| Empty filter | A policy whose filter tree is empty adds no criteria and is skipped. |
Creating Policies via the UI
- Navigate to Governance > Destination Policies in the sidebar
- Click Add Destination Policy
- Select the parent model the policy governs
- Select the destination type it applies to
- Build the filter tree describing which records may be sent
- Name the policy, optionally describe it, and leave Enabled on to enforce it immediately
- Click Save
Policies take effect on the next run of any affected sync. A run already in flight finishes under the rules it started with.
Creating Policies via the API
A policy is scoped to one model and one destination type, and its criteria are a filter tree in the same format as an audience filter. Records that do not match the tree are withheld from that destination type.
# Keep EU customers out of Facebook Ads
curl -X POST "$API_BASE_URL/api/v1/workspaces/$WORKSPACE_ID/destination-rules" \
-H "Authorization: Bearer $API_TOKEN" \
-H "X-Workspace-ID: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "No EU customers to Facebook Ads",
"description": "Withhold EU customer records from Facebook Ads syncs",
"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"]
},
"enabled": true
}'
# Send only opted-in, verified records to Google Ads
curl -X POST "$API_BASE_URL/api/v1/workspaces/$WORKSPACE_ID/destination-rules" \
-H "Authorization: Bearer $API_TOKEN" \
-H "X-Workspace-ID: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "Opted-in and verified only",
"parent_model_id": "770e8400-e29b-41d4-a716-446655440000",
"destination_type": "google_ads",
"filter_tree": {
"type": "and",
"children": [
{
"type": "condition",
"condition_type": "property",
"column": "marketing_opt_in",
"operator": "equals",
"value": true
},
{
"type": "condition",
"condition_type": "property",
"column": "email_verified",
"operator": "equals",
"value": true
}
]
},
"enabled": true
}'See Governance API for the full request and response schemas.
Related Governance Controls
A destination policy answers one question: which records may go to this kind of destination? Three adjacent controls answer the other governance questions people usually ask alongside it.
| To do this | Use | Where |
|---|---|---|
| Stop a column leaving the warehouse at all | Set the column’s sensitivity to Blocked | PII Masking |
| Send a hashed value instead of a raw one | Enable Hash PII (SHA256) on the field mapping | PII Masking |
| Hide a value from operators but still sync it | Set the column’s sensitivity to Redacted or Sync Only | PII Masking |
| Limit how often one profile is contacted | Configure a per-profile frequency cap for the destination type | GET /api/v1/workspaces/{id}/frequency-caps |
| Restrict which records a person can see | Define an access policy and assign it to a group | Access Policies |
The distinction between a destination policy and an access policy is worth keeping straight: an access policy limits what a user can see anywhere in Zeotap, while a destination policy limits what a sync may send, regardless of who set the sync up.
Audit Trail
When a policy is applied to a run, the run’s log records which policy it was and the destination type it applied to. The sync run shows the resulting record counts, so a run that suddenly sends fewer records than expected is usually explained by a policy that was enabled or edited since the previous run.
Changes to policies themselves — creation, edits, enable and disable — are recorded in the workspace audit trail alongside other governance changes. See Data Handling.
Managing Policies
Enabling and Disabling
A policy can be disabled without deleting it. A disabled policy is not looked up at all when a sync runs, so its criteria stop applying immediately — useful for temporarily relaxing a restriction, or for staging a policy before enforcing it.
Because only one enabled policy governs each model-and-destination-type pair, disabling is also how you swap one policy for another on the same pair without a gap in coverage: create the replacement disabled, then disable the old one and enable the new one.
Testing Policies
There is no dry-run mode. To see what a policy will withhold before it takes effect, check the criteria against the same data the policy will read:
- Create the policy with
enabled: false— it is stored but never consulted during syncs - Build an audience on the same parent model with the same criteria, and preview it or run an estimate. Those are the records the policy would permit; everything else in the model is what it would withhold.
- Set
enabled: truewhen the match looks right
Editing a Policy
Edits apply from the next run of any affected sync. Widening a filter does not re-send records that earlier runs withheld — a sync in an incremental mode only sends what has changed since its last run, so records that become newly permitted are picked up when they next change. Trigger a run in a full mode if you need them sent immediately.
Common Patterns
Each pattern below is a single policy on one parent model and one destination type.
| Pattern | Filter tree |
|---|---|
| Keep EU customers out of an ad platform | country not in ["DE", "FR", "IT", "ES", "NL"] |
| Send only marketing-opted-in records | marketing_opt_in equals true |
| Exclude unverified or bounced contacts | email_verified equals true and email_status not equals bounced |
| Send only customers of a given tier | tier in ["gold", "platinum"] |
| Withhold everyone in a suppression audience | not in audience Do Not Contact |
| Send only records with a recent purchase | computed attribute days_since_last_order less than 90 |
To keep a column out of a payload entirely, or to send it hashed, use column sensitivity and hash-on-sync rather than a policy — see Related Governance Controls.
API Reference
Every path below is workspace-scoped — {id} is your workspace ID. See Base URL for your instance’s API base URL and Authentication for the required Authorization and X-Workspace-ID headers.
The API path uses the original destination-rules spelling.
# List all destination policies
GET /api/v1/workspaces/{id}/destination-rules
# Get a single policy
GET /api/v1/workspaces/{id}/destination-rules/{ruleId}
# Create a policy
POST /api/v1/workspaces/{id}/destination-rules
# Update a policy
PUT /api/v1/workspaces/{id}/destination-rules/{ruleId}
# Delete a policy
DELETE /api/v1/workspaces/{id}/destination-rules/{ruleId}
# List the policies that apply to one destination type
GET /api/v1/workspaces/{id}/destination-types/{destType}/rulesSee Governance API for full request/response schemas.
Next Steps
- Set up access policies for row-level access control
- Configure PII masking for column-level controls and hashing
- Configure RBAC to manage who can create and modify policies