Identity-rule simulation
Saving a match rule or survivorship policy takes effect immediately.PUT /cdp/identity-rules and PUT /cdp/survivorship-policy go live the moment the save returns; if a proposed rule over-links (the classic “everyone share email=info@example.com” failure), thousands of contacts can merge before anyone notices, and unwinding a merge is hard. The simulation endpoint is the missing what-if step: it runs the same deterministic engine against a bounded sample of your live contacts and reports what your proposed rule set would do — without writing anything.
This guide covers the simulation endpoint end to end: why you run it before saving, what you send it, and how to read the blast-radius report it returns.
1. Why simulate first
The recommended workflow is to treat simulation as a gate on every identity-rule change:- Run the simulation with your proposed rules.
- Read the blast-radius report.
- Only when the estimate looks right, PUT the rules (and the survivorship policy, if you are changing it).
external_id, email, and phone. When you open an edit on one of those shapes, the identity console refuses to save until you have run a simulation for that shape in the current browser session; attempting to save without one returns a toast naming the simulation to run. The gate is session-scoped: a re-simulation in this same session covers all three of those types until you leave the page, and you re-open it to re-run whenever the estimate was wrong.
Three things never trigger the gate, by design: inserting a priority-only re-order (no new merge risk), deleting a rule (the resolver falls back to the seeded default for that shape), and the anonymous_id shape, which is platform-owned and carries no attribution risk.
2. The endpoint
match_rules (required) is the array being simulated. Each entry sets match_type to one of the strong shapes the deterministic resolver matches on — external_id, email, or phone — plus an enabled flag and a priority between 1 and 1000. These are the shapes a simulation can toggle; the anonymous-ID shape is never a proposed rule here because the engine has no anonymous-ID field on its contact projection. Duplicate match_type entries are rejected with 422.
survivorship_policy (optional) previews which value would win per field on the sample merge pairs, using the same rule shape as the survivorship policy builder: per-field rules with a strategy (one of prefer_target, prefer_source, most_recently_updated, most_recently_created, prefer_non_null, or prefer_source_system plus a source_system for the last one) and an optional default_strategy applied to every field without an explicit rule. If you omit the policy, the simulator resolves the sample pairs against your tenant’s currently active policy, so you see what would happen as the merge would do it today rather than against an empty policy.
auto_merge_threshold (optional, 0–1) sets the probabilistic threshold the simulation uses to decide which cluster counts as an auto-merge versus a review candidate. If omitted, it falls back to the platform default.
over_merge_cluster_size (optional) sets the cluster-size threshold for the over-merge guard. Any proposed cluster bigger than this many profiles is flagged with the offending rule named. Defaults to 25 profiles; you can raise it for very large tenants or lower it when testing a deliberately conservative rule.
sample_size (optional) — the floor is 20. You may ask for more, never fewer, because the review-queue send affordance and the acceptance bar both depend on seeing the matched pairs.
max_contacts (optional) caps how many contacts the simulation scans (default 5,000; hard ceiling 10,000). Like the deterministic preview endpoint, the simulation never pulls a tenant’s whole contact table into one process; when the scan hits the ceiling, truncated reports true so you can raise max_contacts (or accept the bound) rather than silently see a partial picture.
3. Reading the response
A successful response wraps the blast-radius report in the standard data envelope:clusters_formed counts the clusters the proposed rules would stitch; profiles_merged counts secondary members of clusters whose confidence clears the auto-merge threshold (below-threshold clusters stay as proposed review candidates and do not inflate the blast-radius count); max_cluster_size reports the largest cluster’s member count. delta_vs_active diffs those three numbers against your currently active rules, so clusters_formed: 1 reads “one more cluster than today” if positive and “one fewer” if negative.
Sample merge pairs. sample_pairs lists the concrete (primary, secondary) pairs ranked by confidence, each carrying the matched_key that stitched that pair — the match type plus the masked value — and a band of auto_merge or review for how the dashboard would treat the fold-in. sample_pairs_available reports the total count that could have been sampled: when that count is below 20 on a small tenant, the endpoint returns every pair that exists and sets sample_pairs_available to the actual total so the floor is explicitly satisfied, never silently capped.
Survivorship preview. For the first few sample pairs (up to 10), the simulator resolves the proposed (or, if you omitted it, the currently active) survivorship policy against the pairs’ actual field values and reports, per governed field: the field, the strategy applied (explicit rule first, then default_strategy, otherwise the prefer_non_null fallback), the winning_side ("primary" the surviving record or "secondary" the one folded away), and both the winning_value and losing_value. This is the exact same strategy the live merge path applies.
Over-merge guard. When any proposed cluster exceeds over_merge_cluster_size, over_merge_flags names the offending rule directly: the flagged cluster’s primary_id, its cluster_size, the match_type and matched_value_masked that bind the most members in it (the classic “everyone shares email=info@…” failure names the shared value), and that rule’s rule_priority when you supplied one, so the identity console can jump straight to the offending row. over_merge_detected flips to true when at least one flag fired; treat a flagged proposal as not save-ready — narrow the rule set (disable the aggressive shape or raise its priority so a stronger identifier resolves first) and re-run before you save.
A 422 response means the request failed validation — a duplicate match_type, an out-of-range auto_merge_threshold, or a missing source_system on a prefer_source_system rule — and comes back with the standard issues list naming the field and the problem.
4. The read-only guarantee
The simulation never writes. Its handler issues SELECT statements only — a bounded scan of candidate contacts plus a second bounded fetch of full field rows for the first few preview pairs — and never touches the merge path. There is no merge repository or merge call reachable from the simulation controller; the product’s continuous test suite asserts that no write operation can appear there.delta_vs_active exists because the simulation re-runs the current configuration on the same contact sample you are proposing against, so the diff is apples-to-apples.
5. Authorization and rate limits
Simulating touches PII-bearing contact rows even though it never merges, so the endpoint holds to the same posture as the sibling preview and merge-candidates reads: owner, admin, or developer role plus thecontacts:read scope. The endpoint is rate-limited to 20 calls per minute per tenant — heavier than the plain preview’s 30/min because it also resolves survivorship on a handful of pairs.
See also
- Identity rules and the survivorship builder on the CDP pages — the authoring side: match shapes, priority, and the survivorship policy
- Identity resolution and merge semantics — the concept: how identifiers resolve, fold, and propagate
- Identity resolution — the merge review queue and stitching timeline on Audience
- Stitch anonymous sessions into known contacts — why the anonymous-ID shape is never a simulation rule