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

# Customers

> Customer profile, eligibility, simulation, suppressions, summaries, and GDPR-compliant data deletion endpoints.

## GET /api/v1/customers/{customerId}/eligibility

Returns a detailed eligibility report for a customer across all active offers. For each offer, evaluates every decisioning gate and contact policy, returning pass/fail with reasons.

### Response

```json theme={null}
{
  "customerId": "CUST001",
  "customer": {
    "customer_id": "CUST001",
    "age": 35,
    "segment": "high_value"
  },
  "evaluatedAt": "2026-03-30T12:00:00.000Z",
  "summary": {
    "totalOffers": 25,
    "eligibleCount": 12,
    "ineligibleCount": 13,
    "failedQualification": 8,
    "blockedByContactPolicy": 3,
    "blockedBySchedule": 2,
    "noCreatives": 1
  },
  "offers": [
    {
      "offerId": "off_001",
      "offerName": "Platinum Card Upgrade",
      "offerKey": "platinum_upgrade",
      "category": "Credit Cards",
      "subCategory": "Premium",
      "priority": 90,
      "eligible": true,
      "primaryReason": "All rules passed",
      "score": 0.9,
      "rank": 1,
      "scheduleBlocked": false,
      "scheduleReason": null,
      "qualificationPassed": true,
      "failedRules": [],
      "passedRules": [
        {
          "ruleId": "qr_001",
          "ruleName": "High Value Segment",
          "ruleType": "segment_required",
          "scope": "global",
          "eligible": true,
          "reason": null
        }
      ],
      "allRuleResults": [],
      "contactPolicyBlocked": false,
      "blockedPolicies": [],
      "allPolicyResults": [],
      "hasCreatives": true,
      "creativeCount": 3
    },
    {
      "offerId": "off_002",
      "offerName": "Auto Loan Refinance",
      "offerKey": "auto_refi",
      "category": "Lending",
      "subCategory": null,
      "priority": 70,
      "eligible": false,
      "primaryReason": "Failed: Min Credit Score",
      "score": null,
      "rank": null,
      "scheduleBlocked": false,
      "scheduleReason": null,
      "qualificationPassed": false,
      "failedRules": [
        {
          "ruleId": "qr_005",
          "ruleName": "Min Credit Score",
          "ruleType": "attribute_condition",
          "scope": "category",
          "eligible": false,
          "reason": "credit_score < 720"
        }
      ],
      "passedRules": [],
      "allRuleResults": [],
      "contactPolicyBlocked": false,
      "blockedPolicies": [],
      "allPolicyResults": [],
      "hasCreatives": true,
      "creativeCount": 2
    }
  ]
}
```

Offers are sorted: eligible first (by score descending), then ineligible (by priority descending). Each eligible offer receives a `rank` (1-based); ineligible offers have `rank: null`.

### Contact policy result objects

When a contact policy blocks an offer, the `blockedPolicies` array contains objects with:

| Field        | Type    | Description                                                 |
| ------------ | ------- | ----------------------------------------------------------- |
| `policyId`   | string  | Contact policy ID                                           |
| `policyName` | string  | Contact policy name                                         |
| `ruleType`   | string  | `frequency_cap`, `cooldown`, or `time_window`               |
| `blocked`    | boolean | Whether this policy blocked the offer                       |
| `reason`     | string  | Human-readable reason (e.g., `"Daily limit reached (5/5)"`) |

***

## GET /api/v1/customers/{customerId}/profile

Returns a traced decision pipeline for a customer, showing the full evaluation funnel from all offers down to ranked results.

### Response

```json theme={null}
{
  "customer": {
    "customer_id": "CUST001",
    "age": 35,
    "segment": "high_value",
    "tenure_months": 36
  },
  "pipeline": {
    "allOffers": [
      {
        "id": "off_001",
        "name": "Platinum Card Upgrade",
        "priority": 90,
        "category": "Credit Cards",
        "subCategory": "Premium",
        "mandatory": false
      }
    ],
    "afterQualification": [
      { "offerId": "off_001", "offerName": "Platinum Card Upgrade" }
    ],
    "qualificationRejections": [
      {
        "offerId": "off_002",
        "offerName": "Auto Loan Refinance",
        "policyName": "Min Credit Score",
        "policyId": "qr_005",
        "ruleType": "attribute_condition",
        "detail": {
          "type": "attribute_condition",
          "attribute": "credit_score",
          "operator": "gte",
          "expected": 720,
          "actual": 680
        }
      }
    ],
    "offersWithoutCreatives": [],
    "afterContactPolicy": [
      { "offerId": "off_001", "offerName": "Platinum Card Upgrade" }
    ],
    "contactPolicyRejections": [
      {
        "offerId": "off_003",
        "offerName": "Savings Account",
        "creativeId": "cr_010",
        "creativeName": "Email Variant A",
        "policyName": "Daily Frequency Cap",
        "policyId": "cp_001",
        "ruleType": "frequency_cap",
        "detail": {
          "type": "frequency_cap",
          "period": "daily",
          "max": 3,
          "actual": 3
        }
      }
    ],
    "rankedResults": [
      {
        "creativeId": "cr_001",
        "creativeName": "Email Hero",
        "offerId": "off_001",
        "offerName": "Platinum Card Upgrade",
        "category": "Credit Cards",
        "subCategory": "Premium",
        "channelType": "email",
        "channelName": "Marketing Email",
        "channelId": "ch_001",
        "templateType": "html",
        "weight": 80,
        "score": 0.72,
        "priority": 90,
        "rank": 1
      }
    ]
  },
  "interactionHistory": [
    {
      "id": "ih_001",
      "timestamp": "2026-03-28T10:30:00.000Z",
      "offerName": "Platinum Card Upgrade",
      "creativeName": "Email Hero",
      "channelId": "ch_001",
      "interactionType": "click",
      "outcomeTypeKey": "click",
      "direction": "outbound",
      "rank": 1
    }
  ],
  "summaries": [
    {
      "periodType": "alltime",
      "periodKey": "alltime",
      "customerId": "CUST001",
      "offerId": "off_001",
      "impressions": 12,
      "positive": 4,
      "negative": 1,
      "converts": 2
    }
  ]
}
```

### Qualification rejection detail types

The `detail` object in `qualificationRejections` varies by rule type:

| Rule Type              | Detail Fields                                 |
| ---------------------- | --------------------------------------------- |
| `segment_required`     | `required` (string\[]), `actual` (string\[])  |
| `attribute_condition`  | `attribute`, `operator`, `expected`, `actual` |
| `propensity_threshold` | `model`, `minScore`, `actualScore`            |
| `recency_check`        | `attribute`, `maxDays`, `actualDays`          |

### Contact policy rejection detail types

| Rule Type              | Detail Fields                                                                                         |
| ---------------------- | ----------------------------------------------------------------------------------------------------- |
| `frequency_cap`        | `period`, `max`, `actual`                                                                             |
| `cooldown`             | `cooldownHours`, `hoursSinceLast`                                                                     |
| `budget_exhausted`     | `budgetField`, `threshold`, `actual`                                                                  |
| `outcome_based`        | `afterOutcome`, `suppressForDays`, `daysSince`, `lastOutcome`                                         |
| `segment_exclusion`    | `excludeSegments` (string\[]), `matchedSegment`                                                       |
| `time_window`          | `allowedDays`, `currentDay`, `startHour`, `endHour`, `currentHour`                                    |
| `mutual_exclusion`     | `offerGroup` (string\[]), `conflictingOfferId`                                                        |
| `category_suppression` | `categoryId`, `subCategoryId`, `suppressionDays`, `daysSince`, `triggeringOfferId`, `suppressedUntil` |

***

## POST /api/v1/customers/{customerId}/simulate

Runs the Recommend API internally for a customer and returns the full decision result including scores, rankings, and channel assignments. Always includes debug information.

### Request Body

| Field       | Type    | Required | Description                                        |
| ----------- | ------- | -------- | -------------------------------------------------- |
| `channel`   | string  | No       | Filter by channel                                  |
| `placement` | string  | No       | Filter by placement                                |
| `limit`     | integer | No       | Max results (default: 20)                          |
| `flowId`    | string  | No       | Decision flow ID or key (alias: `decisionFlowKey`) |

### Response

```json theme={null}
{
  "customerId": "CUST001",
  "simulatedAt": "2026-03-30T12:00:00.000Z",
  "success": true,
  "channel": "email",
  "placement": "all",
  "flowId": "auto-resolved",
  "result": {
    "recommendations": [],
    "debug": {}
  }
}
```

### Error Response

If the internal recommend call fails:

```json theme={null}
{
  "customerId": "CUST001",
  "simulatedAt": "2026-03-30T12:00:00.000Z",
  "error": "No active decision flow found",
  "success": false
}
```

***

## GET /api/v1/customers/{customerId}/suppressions

Returns active suppressions for a customer with resolved offer names.

### Response

```json theme={null}
{
  "data": [
    {
      "id": "sup_001",
      "scope": "offer",
      "scopeId": "off_001",
      "offerName": "Platinum Card Upgrade",
      "policyType": "frequency_cap",
      "reason": "Daily limit reached (5/5)",
      "triggerCount": 5,
      "expiresAt": "2026-04-01T00:00:00.000Z",
      "createdAt": "2026-03-30T10:00:00.000Z",
      "isActive": true
    }
  ]
}
```

| Field          | Type           | Description                                                     |
| -------------- | -------------- | --------------------------------------------------------------- |
| `scope`        | string         | Suppression scope (`offer`, `category`, `global`)               |
| `scopeId`      | string         | ID of the scoped entity                                         |
| `offerName`    | string or null | Resolved offer name (only when scope is `offer`)                |
| `policyType`   | string         | Contact policy type that triggered the suppression              |
| `triggerCount` | integer        | Number of times the suppression was triggered                   |
| `expiresAt`    | string         | ISO timestamp when the suppression expires                      |
| `isActive`     | boolean        | Whether the suppression is currently active (`expiresAt > now`) |

***

## GET /api/v1/customers/{customerId}/summaries

Returns interaction summary aggregates for a specific customer, including per-offer breakdowns and rolled-up totals.

### Query Parameters

| Parameter    | Type   | Required | Description                                          |
| ------------ | ------ | -------- | ---------------------------------------------------- |
| `periodType` | string | No       | Filter: `daily`, `weekly`, `monthly`, `alltime`      |
| `periodKey`  | string | No       | Specific period key (e.g., `2026-03-30`, `2026-W13`) |
| `offerId`    | string | No       | Filter by specific offer                             |
| `channelId`  | string | No       | Filter by specific channel                           |

### Response

```json theme={null}
{
  "customerId": "CUST001",
  "totals": {
    "impressions": 156,
    "positive": 42,
    "negative": 18,
    "neutral": 96,
    "converts": 12,
    "totalValue": 4800.50,
    "overallConversionRate": 0.0769
  },
  "byOffer": [
    {
      "offerId": "off_001",
      "offerName": "Platinum Card Upgrade",
      "impressions": 45,
      "positive": 15,
      "negative": 5,
      "converts": 4,
      "totalValue": 1200.00,
      "conversionRate": 0.0889,
      "lastOutcomeKey": "convert",
      "lastContactAt": "2026-03-28T10:30:00.000Z"
    }
  ],
  "raw": [
    {
      "periodType": "alltime",
      "periodKey": "alltime",
      "offerId": "off_001",
      "offerName": "Platinum Card Upgrade",
      "channelId": "ch_001",
      "impressions": 45,
      "positive": 15,
      "negative": 5,
      "neutral": 25,
      "converts": 4,
      "totalValue": 1200.00,
      "lastOutcomeKey": "convert",
      "lastContactAt": "2026-03-28T10:30:00.000Z",
      "direction": "outbound"
    }
  ],
  "meta": {
    "summaryCount": 12,
    "periodTypes": ["daily", "weekly", "alltime"],
    "queriedAt": "2026-03-30T12:00:00.000Z"
  }
}
```

***

## DELETE /api/v1/customers/{customerId}

Deletes all stored data for a customer. Supports GDPR right-to-erasure (DSAR) workflows. Deletes interaction history, interaction summaries, experiment assignments, and impressions in foreign-key-safe order.

### Response

```json theme={null}
{
  "success": true,
  "deleted": {
    "interactionHistory": 142,
    "interactionSummary": 23,
    "experimentAssignments": 0,
    "impressions": 67
  }
}
```

### Error codes

| Code  | Reason                           |
| ----- | -------------------------------- |
| `403` | Caller does not have admin role. |

<Warning>This permanently deletes customer data. This operation cannot be undone. An audit event is logged for every deletion.</Warning>

## DELETE /api/v1/customers/{customerId}/data

Scoped, GDPR-compliant deletion of customer data with a confirmation safeguard. The route at `src/app/api/v1/customers/[customerId]/data/route.ts:24-122` requires the caller to set the `X-Confirm-Delete` header to the same `customerId` in the path — a missing or mismatched header returns `400` so a stray DELETE call cannot purge data by accident.

Three scopes are supported via the `scope` query parameter (`route.ts:46-50`):

| Scope          | Tables affected                                                                              |
| -------------- | -------------------------------------------------------------------------------------------- |
| `interactions` | `interaction_history`, `interaction_summary`                                                 |
| `traces`       | `decision_traces`                                                                            |
| `all`          | `interactions` + `traces` + `experiment_assignments` + `impressions` (DSAR-style full purge) |

Default scope is `all` (`route.ts:44`).

### Path Parameters

| Parameter    | Type   | Description                   |
| ------------ | ------ | ----------------------------- |
| `customerId` | string | Customer identifier to purge. |

### Query Parameters

<ParamField query="scope" type="string" default="all">
  One of `interactions`, `traces`, `all`. Any other value returns `400`.
</ParamField>

### Required headers

| Header             | Required             | Read at         | Purpose                                                             |
| ------------------ | -------------------- | --------------- | ------------------------------------------------------------------- |
| `X-Confirm-Delete` | Yes                  | `route.ts:36`   | Must equal the `customerId` path parameter. Mismatch returns `400`. |
| `X-API-Key`        | Yes (one of the two) | `tenant.ts:97`  | API key (`krn_…`)                                                   |
| `X-Tenant-Id`      | Yes (one of the two) | `tenant.ts:113` | Direct tenant id                                                    |

### Response

Returned at `route.ts:112-118`.

```json theme={null}
{
  "success": true,
  "customerId": "cust_42",
  "scope": "all",
  "totalDeleted": 232,
  "deleted": {
    "interactionHistory": 142,
    "interactionSummary": 23,
    "decisionTraces": 0,
    "experimentAssignments": 0,
    "impressions": 67
  }
}
```

<ResponseField name="totalDeleted" type="number">
  Sum of all per-table counts.
</ResponseField>

<ResponseField name="deleted" type="object">
  Per-table delete counts. Tables outside the requested scope are absent. `decisionTraces`, `experimentAssignments`, and `impressions` are wrapped in try/catch — if the table is unavailable the count is reported as `0` rather than failing the request (`route.ts:74-97`).
</ResponseField>

### Status codes

| Code | When                                            | Source                     |
| ---- | ----------------------------------------------- | -------------------------- |
| 200  | Deletion succeeded                              | —                          |
| 400  | Missing or mismatched `X-Confirm-Delete` header | Header guard               |
| 400  | Invalid `scope` query value                     | Query validation           |
| 401  | Caller is not authenticated                     | Role gate                  |
| 403  | Caller is not `admin`                           | Role gate (admin required) |
| 500  | Unexpected error                                | —                          |

### Audit

Every successful call writes one `delete` row to the audit log with `entityType: "customer_data"` and the per-table counts in `changes`. The audit row is fire-and-forget — a failed log write does not roll back the deletion.

### Roles

admin only.

<Warning>
  Permanent deletion. The operation cannot be undone. Use this endpoint when fulfilling a DSAR/right-to-erasure request — for soft suppression of a single customer in active campaigns, use the [Suppressions API](/api-reference/suppressions) instead.
</Warning>

***

***

## Role requirements

| Endpoint                           | Allowed Roles         |
| ---------------------------------- | --------------------- |
| `GET /customers/{id}/eligibility`  | admin, editor, viewer |
| `GET /customers/{id}/profile`      | admin, editor, viewer |
| `POST /customers/{id}/simulate`    | admin, editor, viewer |
| `GET /customers/{id}/suppressions` | admin, editor, viewer |
| `GET /customers/{id}/summaries`    | admin, editor, viewer |
| `DELETE /customers/{id}`           | admin                 |
| `DELETE /customers/{id}/data`      | admin                 |
