Skip to main content
The Why-Not API performs on-demand diagnostic analysis of the decision pipeline for a specific customer-offer pair. It evaluates all qualification rules and contact policies, returning a detailed breakdown with pass/block/skip status and human-readable reasons for each.
See the Action Analysis (Why-Not) feature page for business context, interpretation guidance, and common workflows.

Base Path

/api/v1/customers/{customerId}/why-not/{offerId}

GET /api/v1/customers//why-not/

Run a Why-Not analysis explaining why a specific offer is eligible or blocked for a specific customer. Roles: admin, editor, viewer

Path Parameters

ParameterTypeDescription
customerIdstringCustomer identifier (matches customer_id in schema tables)
offerIdstringOffer identifier

Response 200

{
  "customerId": "CUST-001",
  "offerId": "offer_summer_promo",
  "offerName": "Summer Rewards Bonus",
  "offerStatus": "active",
  "productType": "rewards",
  "category": "Retention",

  "verdict": "blocked",
  "summary": "Blocked by contact policy: Email Frequency Cap",

  "qualification": {
    "total": 3,
    "passed": 3,
    "blocked": 0,
    "skipped": 1,
    "details": [
      {
        "ruleId": "qr_001",
        "ruleName": "Gold Tier Only",
        "ruleType": "attribute_condition",
        "scope": "category",
        "applies": true,
        "result": "passed",
        "reason": "Passed"
      },
      {
        "ruleId": "qr_002",
        "ruleName": "Min Spend $500",
        "ruleType": "attribute_condition",
        "scope": "global",
        "applies": true,
        "result": "passed",
        "reason": "Passed"
      },
      {
        "ruleId": "qr_003",
        "ruleName": "Age 18+",
        "ruleType": "attribute_condition",
        "scope": "global",
        "applies": true,
        "result": "passed",
        "reason": "Passed"
      },
      {
        "ruleId": "qr_004",
        "ruleName": "Credit Score Check",
        "ruleType": "attribute_condition",
        "scope": "offer",
        "applies": false,
        "result": "skipped",
        "reason": "Rule scope offer:offer_credit_card does not match this offer"
      }
    ]
  },

  "contactPolicy": {
    "total": 2,
    "passed": 1,
    "blocked": 1,
    "skipped": 0,
    "details": [
      {
        "policyId": "cp_001",
        "policyName": "Email Frequency Cap",
        "ruleType": "frequency_cap",
        "scope": "global",
        "applies": true,
        "result": "blocked",
        "reason": "Frequency cap exceeded: 5/3 (daily)"
      },
      {
        "policyId": "cp_002",
        "policyName": "48h Cooldown",
        "ruleType": "cooldown",
        "scope": "global",
        "applies": true,
        "result": "passed",
        "reason": "Cooldown expired: 72.5h since last contact (threshold: 48h)"
      }
    ]
  },

  "customerData": {
    "age": 34,
    "income": 75000,
    "gender": "F",
    "membershipDays": 412
  },

  "interactionHistory": {
    "totalImpressions": 5,
    "lastContact": "2026-04-02T14:30:00.000Z",
    "summaryCount": 3
  }
}

Top-Level Response Fields

FieldTypeDescription
customerIdstringThe customer analyzed
offerIdstringThe offer analyzed
offerNamestringHuman-readable offer name
offerStatusstringCurrent offer status (active, paused, etc.)
productTypestringOffer product type, if set
categorystringCategory name the offer belongs to
verdictstringFinal outcome: eligible or blocked
summarystringHuman-readable explanation of the verdict

Qualification Object

FieldTypeDescription
totalintegerNumber of rules that apply to this offer
passedintegerRules that passed
blockedintegerRules that blocked the offer
skippedintegerRules that do not apply (scope mismatch)
detailsarrayPer-rule breakdown (see below)

Qualification Detail Fields

FieldTypeDescription
ruleIdstringQualification rule ID
ruleNamestringRule name
ruleTypestringRule type: attribute_condition, offer_attribute, segment_required, propensity_threshold, recency_check, metric_condition
scopestringRule scope: global, category, subcategory, offer
appliesbooleanWhether this rule applies to the given offer
resultstringpassed, blocked, or skipped
reasonstringHuman-readable explanation

Contact Policy Object

Same structure as the qualification object, with these detail fields:
FieldTypeDescription
policyIdstringContact policy ID
policyNamestringPolicy name
ruleTypestringPolicy type: frequency_cap, cooldown
scopestringPolicy scope: global, offer, action, category, channel
appliesbooleanWhether this policy applies to the given offer
resultstringpassed, blocked, or skipped
reasonstringHuman-readable explanation (e.g., “Frequency cap exceeded: 5/3 (daily)“)

Customer Data Object

FieldTypeDescription
agenumber or nullCustomer age from enrichment
incomenumber or nullCustomer income from enrichment
genderstring or nullCustomer gender from enrichment
membershipDaysnumber or nullDays since membership started

Interaction History Object

FieldTypeDescription
totalImpressionsintegerTotal impressions for this customer-offer pair
lastContactstring or nullISO 8601 timestamp of last contact
summaryCountintegerNumber of interaction summary records

Error Codes

StatusCodeDescription
401UnauthorizedMissing or invalid authentication
403ForbiddenInsufficient role
404Not FoundOffer not found in the current tenant
500Server ErrorInternal error during analysis

Example — Offer Not Found

{
  "title": "Not found",
  "detail": "Offer not found"
}

Example

curl https://playground.kaireonai.com/api/v1/customers/CUST-001/why-not/offer_summer_promo \
  -H "X-Tenant-Id: my-tenant" \
  -H "Authorization: Bearer <token>"

See Also