Contact policies control how often and under what conditions a customer can be contacted. They enforce frequency caps, cooldowns, budget exhaustion, mutual exclusion, and other suppression rules during the filtering stage of a Decision Flow.Documentation Index
Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt
Use this file to discover all available pages before exploring further.
See the Contact Policies feature page for UI guidance and conceptual overview.
Base path
List contact policies
Query parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
limit | No | integer | Maximum results per page. Default 25. |
cursor | No | string | Cursor for keyset pagination. |
Response 200
Create a contact policy
admin role.
Request body
| Field | Required | Type | Description |
|---|---|---|---|
name | Yes | string (1-255) | Unique policy name. |
description | No | string | Policy description. |
status | No | enum | draft, active (default), paused, archived. |
scope | No | enum | global, offer (default), creative, channel, category, subcategory. |
scopeId | No | string | null | ID of the scoped entity (required when scope is not global). |
ruleType | Yes | enum | One of the rule types below. |
config | No | object | Rule-type-specific configuration. |
priority | No | integer (0-100) | Evaluation priority (higher = evaluated first). Default 50. |
scopes | No | array | Array of scope assignments. Each item: { scope: "global"|"offer"|"creative"|"channel"|"category"|"subcategory", scopeId: "entity-UUID" }. When provided, overrides the legacy scope/scopeId fields. A policy can have multiple scope assignments simultaneously. |
Rule types and config shapes
Twelve rule types are supported. Each has a specificconfig object shape:
| Type | Description | Config shape |
|---|---|---|
frequency_cap | Limit contacts within rolling time windows. Optional campaign filters scope counts to or away from specific batch campaign runs. | { maxPerDay?, maxPerWeek?, maxPerMonth?, maxTotal?, withinCampaignId?: string, excludeCampaignIds?: string[] } |
cooldown | Minimum hours between contacts. | { cooldownHours: number } |
budget_exhausted | Suppress when impression or spend budget is consumed. | { budgetField: "impressions" | "spend", threshold: number } |
outcome_based | Suppress after a specific outcome — or any outcome from a configured set. | { afterOutcome: string | string[], suppressForDays: number } |
segment_exclusion | Exclude customers in named segments. | { excludeSegments: string[] } |
time_window | Restrict to specific hours/days. | { startHour?, endHour?, daysOfWeek?, timezone? } |
mutual_exclusion | Prevent conflicting offers from being served together. | { excludeOfferIds: string[] } |
cross_channel_cap | Frequency cap aggregated across all channels for one Offer. Optional appliesAcross narrows the count to a specific channel-id list (omit / empty = all channels, legacy behaviour). | { maxTotal: number, periodType?: "daily" | "weekly" | "monthly", appliesAcross?: string[] } |
customer_total_cap | Customer Communication Budget — total contacts a single customer receives across all Offers, channels, and creatives in a window. | { maxTotal: number, periodType?: "daily" | "weekly" | "monthly" | "alltime" } |
offer_category_cap | Cap total contacts a customer receives in a specific Offer.category (free-form marketing string) per period. | { targetCategory: string, maxTotal: number, periodType?: "daily" | "weekly" | "monthly" | "alltime" } |
allow_override | Allowlist that bypasses other blocking policies. | { reason?: string } |
category_suppression | Suppress all offers in a category after any was shown. | { categoryId?, subCategoryId?, suppressionDays: number } |
Campaign-aware
frequency_cap: withinCampaignId (when set) restricts the cap to summary rows whose campaignId matches that Run.id — other campaigns and non-campaign sends are ignored. excludeCampaignIds drops impressions from the listed campaigns; rows with a null campaignId are still counted. InteractionHistory and InteractionSummary carry an additive nullable campaignId column populated by the writer when one is supplied.For
outcome_based rules, config.afterOutcome accepts either a non-empty string (legacy single-outcome form) or a non-empty array of strings (preferred). Empty values are rejected with outcome_based requires afterOutcome as a non-empty string or non-empty string array. config.suppressForDays must be >= 0. The Studio UI exposes a one-click Adverse Outcomes preset that fills the array with ["complaint", "unsubscribe", "hard_bounce", "spam_report"] for the standard 90-day compliance quarantine.Example request
Response 201
Returns the created contact policy object.
Validation
All fields are validated via Zod schemas:name: 1-255 characters, must be unique per tenant.ruleType: Must be one of the twelve enum values listed above. Forcustomer_total_cap,config.maxTotalis required (non-negative number) andconfig.periodType(if set) must be one ofdaily,weekly,monthly,alltime. Foroffer_category_cap,config.targetCategory(non-empty string) andconfig.maxTotal(non-negative number) are required, andconfig.periodTypefollows the same enum. Forfrequency_cap,config.withinCampaignId(when set) must be a non-empty string andconfig.excludeCampaignIds(when set) must be a non-empty string array.scope: Must be one of:global,offer,creative,channel,category,subcategory.priority: Integer, 0-100.config: Free-form JSON object (structural validation per rule type is not enforced by the schema).
Error codes
| Code | Reason |
|---|---|
400 | Validation error (missing name or ruleType, invalid timezone, invalid scope/priority). |
409 | A contact policy with that name already exists. |
415 | Content-Type is not application/json. |
Update a contact policy
admin role.
Request body
All fields from the create schema are accepted as optional, plus:| Field | Required | Type | Description |
|---|---|---|---|
id | Yes | string | The policy ID to update. |
Example request
Response 200
Returns the updated contact policy object.
Delete a contact policy
draftConfig.
Query parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
id | Yes | string | Policy ID to delete. |
Response 200
This endpoint uses soft-delete — the record is not physically removed from the database. It is excluded from GET results by default. To include soft-deleted records, pass
?includeDeleted=true on the GET request.The API checks all Decision Flows in the tenant for references to this policy ID in their
draftConfig.stages.filter.contactPolicyIds array and returns warnings for any matches.Error codes
| Code | Reason |
|---|---|
400 | Missing id query parameter, or soft-delete failed. |
Role requirements
| Method | Minimum role |
|---|---|
| GET | viewer |
| POST | admin |
| PUT | admin |
| DELETE | admin |
Soft-delete and audit
Contact policies use soft-delete with audit snapshots. When a policy is deleted:- The
deletedAttimestamp is set (record is retained). - An audit snapshot is captured with the full state before deletion.
- Ghost reference warnings are returned if the policy is still referenced by any Decision Flow.
auditedUpdate, incrementing the rowVersion on each change.
To include soft-deleted policies in GET responses, add ?includeDeleted=true to the query string.
Multi-scope policies are evaluated differently: global-scoped policies are evaluated in the Decision Flow’s Filter node, while entity-scoped policies (offer, channel, creative) are automatically evaluated per-candidate during the recommend pipeline.
Preview policy impact
frequency_cap-style policy would have affected
recent traffic. Samples up to 1,000 active customers, counts matching
interactions in the lookback window, and returns affected counts plus an
optional segment breakdown.
Request body
| Field | Required | Type | Description |
|---|---|---|---|
policyType | Yes | string | Currently only frequency_cap is fully supported by the count semantics. |
scope | No | enum | global (default), offer, category, channel. |
scopeId | No | string | null | ID of the scoped entity (required when scope is not global). |
config.maxFrequency | Yes | integer (>= 1) | The cap value to project against. |
config.periodDays | Yes | integer (1-365) | Lookback window. |
config.outcomeType | Yes | string | Interaction type to count (typically impression). |
sampleSize | No | integer (100-10000) | Customer sample size. Default 1000. |
Response 200
frequency_cap config panel. The button derives maxFrequency
and periodDays from the largest cap on the form (daily → 1 day, weekly → 7,
monthly → 30, total → 365).
Contact Policies
Learn more about configuring contact policies in the platform UI.