> ## 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.

# Why-Not API

> Action analysis endpoint — explains exactly why an offer was or wasn't shown to a specific customer.

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.

<Info>
  See the [Action Analysis (Why-Not) feature page](/decisioning/why-not-analysis) for business context, interpretation guidance, and common workflows.
</Info>

## Base Path

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

***

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

Run a Why-Not analysis explaining why a specific offer is eligible or blocked for a specific customer.

**Roles:** any authenticated user (no specific role required)

### Path Parameters

| Parameter    | Type   | Description                                                  |
| ------------ | ------ | ------------------------------------------------------------ |
| `customerId` | string | Customer identifier (matches `customer_id` in schema tables) |
| `offerId`    | string | Offer identifier                                             |

### Response `200`

```json theme={null}
{
  "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

| Field         | Type   | Description                                     |
| ------------- | ------ | ----------------------------------------------- |
| `customerId`  | string | The customer analyzed                           |
| `offerId`     | string | The offer analyzed                              |
| `offerName`   | string | Human-readable offer name                       |
| `offerStatus` | string | Current offer status (`active`, `paused`, etc.) |
| `productType` | string | Offer product type, if set                      |
| `category`    | string | Category name the offer belongs to              |
| `verdict`     | string | Final outcome: `eligible` or `blocked`          |
| `summary`     | string | Human-readable explanation of the verdict       |

### Qualification Object

| Field     | Type    | Description                              |
| --------- | ------- | ---------------------------------------- |
| `total`   | integer | Number of rules that apply to this offer |
| `passed`  | integer | Rules that passed                        |
| `blocked` | integer | Rules that blocked the offer             |
| `skipped` | integer | Rules that do not apply (scope mismatch) |
| `details` | array   | Per-rule breakdown (see below)           |

### Qualification Detail Fields

| Field      | Type    | Description                                                                                                                          |
| ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `ruleId`   | string  | Qualification rule ID                                                                                                                |
| `ruleName` | string  | Rule name                                                                                                                            |
| `ruleType` | string  | Rule type: `attribute_condition`, `offer_attribute`, `segment_required`, `propensity_threshold`, `recency_check`, `metric_condition` |
| `scope`    | string  | Rule scope: `global`, `category`, `subcategory`, `offer`                                                                             |
| `applies`  | boolean | Whether this rule applies to the given offer                                                                                         |
| `result`   | string  | `passed`, `blocked`, or `skipped`                                                                                                    |
| `reason`   | string  | Human-readable explanation                                                                                                           |

### Contact Policy Object

Same structure as the qualification object, with these detail fields:

| Field        | Type    | Description                                                              |
| ------------ | ------- | ------------------------------------------------------------------------ |
| `policyId`   | string  | Contact policy ID                                                        |
| `policyName` | string  | Policy name                                                              |
| `ruleType`   | string  | Policy type: `frequency_cap`, `cooldown`                                 |
| `scope`      | string  | Policy scope: `global`, `offer`, `action`, `category`, `channel`         |
| `applies`    | boolean | Whether this policy applies to the given offer                           |
| `result`     | string  | `passed`, `blocked`, or `skipped`                                        |
| `reason`     | string  | Human-readable explanation (e.g., "Frequency cap exceeded: 5/3 (daily)") |

### Customer Data Object

| Field            | Type           | Description                     |
| ---------------- | -------------- | ------------------------------- |
| `age`            | number or null | Customer age from enrichment    |
| `income`         | number or null | Customer income from enrichment |
| `gender`         | string or null | Customer gender from enrichment |
| `membershipDays` | number or null | Days since membership started   |

### Interaction History Object

| Field              | Type           | Description                                    |
| ------------------ | -------------- | ---------------------------------------------- |
| `totalImpressions` | integer        | Total impressions for this customer-offer pair |
| `lastContact`      | string or null | ISO 8601 timestamp of last contact             |
| `summaryCount`     | integer        | Number of interaction summary records          |

***

## Error Codes

| Status | Code         | Description                           |
| ------ | ------------ | ------------------------------------- |
| `401`  | Unauthorized | Missing or invalid authentication     |
| `404`  | Not Found    | Offer not found in the current tenant |
| `500`  | Server Error | Internal error during analysis        |

### Example — Offer Not Found

```json theme={null}
{
  "title": "Not found",
  "detail": "Offer not found"
}
```

***

## Example

```bash theme={null}
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

* [Action Analysis (Why-Not)](/decisioning/why-not-analysis)
* [Qualification Rules](/api-reference/qualification-rules)
* [Contact Policies](/api-reference/contact-policies)
* [Decision Traces](/api-reference/decision-traces)
* [Live Console](/operations-reporting/ops-manager)
