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

# Offers

> The core decisioning unit — define what you can recommend to customers with priority, budget, scheduling, and qualification controls.

<Note>
  **See also**: [Offers REST API reference](/api-reference/offers) for request/response shapes, status codes, and error semantics.
</Note>

An **Offer** is the fundamental building block of KaireonAI. It represents something you can recommend to a customer — a product, a promotion, a message, or a next-best-action. Every time the Recommend API runs, it evaluates your active offers against a specific customer, filters out ineligible ones, scores the rest, and returns the best matches. If Decision Flows are the engine, Offers are the fuel.

Offers belong to a [Category](/studio/business-hierarchy) (and optionally a sub-category), carry configuration for priority, business value, budget, and scheduling, and connect to [Creatives](/studio/creatives) that define the actual content delivered through each [Channel](/studio/channels).

<Info>
  Only offers with `status: "active"` are considered by Decision Flows. Draft and paused offers are excluded from all candidate selection.
</Info>

***

## How Offers Work at Runtime

When a [Decision Flow](/decisioning/decision-flows) executes via the Recommend API, every offer passes through these stages:

| Stage              | Regular Offers                                  | Mandatory Offers                                  |
| ------------------ | ----------------------------------------------- | ------------------------------------------------- |
| **Inventory**      | Loaded by scope (all, category, or manual)      | Same — split into separate list                   |
| **Schedule**       | Excluded if outside date/time window            | Same — expired promotions are not forced          |
| **Qualification**  | Evaluated against propensity, segments, recency | **Bypassed**                                      |
| **Scoring**        | PRIE formula with model + weights               | Fixed score of **1.0**                            |
| **Contact Policy** | Filtered by all active policies                 | **Bypassed** (except mandatory-specific policies) |
| **Daily Cap**      | N/A                                             | Capped at 5/customer/day (configurable)           |
| **Ranking**        | Sorted by score descending                      | Prepended above regular results                   |

***

## Common Use Cases

| Use Case                     | Example                                       | Key Config                                                              |
| ---------------------------- | --------------------------------------------- | ----------------------------------------------------------------------- |
| **Product recommendation**   | "Upgrade to Premium Card"                     | `priority: 70`, `businessValue: 85`, decisioning gates for credit score |
| **Time-sensitive promotion** | "Flash Sale: 24 Hours Only"                   | `schedule.startDate` / `endDate`, high priority                         |
| **Regulatory notice**        | "CFPB Rate Disclosure"                        | `mandatory: true` with reason, approver, and expiry                     |
| **Retention offer**          | "Stay with us — get 20% off for 6 months"     | High `businessValue`, churn model for propensity scoring                |
| **Exploratory content**      | "Did you know about our new savings account?" | Low `priority: 25`, broad eligibility                                   |

***

## Priority and Business Value

Two fields control how aggressively an offer competes during scoring. They look similar but serve different purposes in the [PRIE formula](/decisioning/decision-flows#prie-ranking-formula):

| Field           | PRIE Factor      | Range  | Purpose                                                              | Who Sets It      |
| --------------- | ---------------- | ------ | -------------------------------------------------------------------- | ---------------- |
| `priority`      | **Emphasis (E)** | 0--100 | Subjective campaign lever — how much to boost or suppress this offer | Marketer         |
| `businessValue` | **Impact (I)**   | 0--100 | Objective business worth — revenue, margin, strategic importance     | Business analyst |

Both are normalized to 0--1 at scoring time. **Setting either to 0 eliminates the offer from PRIE scoring entirely** (because PRIE is multiplicative).

### Priority Bands

| Level        | Value | When to Use                         |
| ------------ | ----- | ----------------------------------- |
| **Critical** | 90    | Regulatory, compliance              |
| **High**     | 70    | Strategic campaigns, time-sensitive |
| **Medium**   | 50    | Standard recommendations (default)  |
| **Low**      | 25    | Exploratory, filler content         |

### Business Value Example (Retail Rewards)

| Offer                    | `businessValue` | Normalized (I) | Rationale                                          |
| ------------------------ | --------------- | -------------- | -------------------------------------------------- |
| BOGO Iced Beverage       | 80              | 0.80           | High margin on iced beverages, drives store visits |
| 25% Off Merchandise      | 50              | 0.50           | Lower margin but drives basket size                |
| Informational: New Store | 20              | 0.20           | No direct revenue, brand awareness only            |

<Tip>
  When in doubt, start with `priority: 50` and `businessValue: 50` (the defaults). Adjust after you see initial performance data in the [Business Dashboard](/operations-reporting/dashboards#business-dashboard).
</Tip>

***

## Status Lifecycle

Offers progress through a defined lifecycle:

```
draft -> active -> paused -> archived
```

| Status     | Description                                               |
| ---------- | --------------------------------------------------------- |
| `draft`    | Being configured. Not available for decisioning.          |
| `active`   | Live and eligible for recommendation.                     |
| `paused`   | Temporarily removed from decisioning. Can be reactivated. |
| `archived` | Permanently retired. Preserved for reporting history.     |

***

## Qualification Config

Control which customers are eligible to receive this offer via the `eligibility` object:

| Field                 | Type          | Description                                          | Example                                              |
| --------------------- | ------------- | ---------------------------------------------------- | ---------------------------------------------------- |
| `propensityThreshold` | number (0--1) | Minimum model score required                         | `0.65` — only show to customers with 65%+ propensity |
| `recency`             | number (days) | Minimum days since customer last received this offer | `30` — no repeat within a month                      |
| `requiredSegments`    | string\[]     | Customer must belong to all listed segments          | `["high_value", "credit_eligible"]`                  |
| `modelReference`      | string        | Scoring model to use for propensity evaluation       | `"model_propensity_cc_v3"`                           |

***

## Budget Config

Set spend and impression limits via the `budget` object:

| Field            | Type   | Description                       | Example   |
| ---------------- | ------ | --------------------------------- | --------- |
| `maxImpressions` | number | Total lifetime impression cap     | `50000`   |
| `maxDailyBudget` | number | Maximum spend per day             | `2500.00` |
| `costPerAction`  | number | Cost per impression or conversion | `1.50`    |

Budget checking uses atomic Redis counter increments (with Prisma fallback) to prevent race conditions under concurrent load. When an offer's budget is exhausted, it is automatically excluded from decisioning.

<Note>
  The `budget_exhausted` [Contact Policy](/decisioning/contact-policies) rule type can also enforce budget limits at the policy level.
</Note>

## Inventory Config

Track finite stock for an offer via the `inventory` object:

| Field            | Type   | Description                                                                                        | Example |
| ---------------- | ------ | -------------------------------------------------------------------------------------------------- | ------- |
| `totalStock`     | number | Starting stock. Leave unset to disable inventory tracking.                                         | `500`   |
| `remainingStock` | number | Units left. Defaults to `totalStock`; decrements on each positive outcome recorded via `/respond`. | `500`   |

When `totalStock` is set and `remainingStock` reaches `0`, the offer is excluded from batch decisioning (`inventory_out_of_stock`), and in realtime decisioning its remaining stock feeds the Lagrangian ranking constraint the same way `budget` does. Recording a conversion for the offer atomically decrements `remainingStock` (serialized to prevent oversell). An offer with no `totalStock` is treated as unlimited.

***

## Schedule Config

Restrict when an offer is eligible via the `schedule` object:

| Field        | Type      | Description                                            | Example                                |
| ------------ | --------- | ------------------------------------------------------ | -------------------------------------- |
| `startDate`  | ISO 8601  | Earliest date the offer can be recommended             | `"2026-03-15T00:00:00Z"`               |
| `endDate`    | ISO 8601  | Latest date the offer can be recommended               | `"2026-06-15T23:59:59Z"`               |
| `daysOfWeek` | number\[] | Allowed days (0=Sunday through 6=Saturday)             | `[1,2,3,4,5]` — weekdays only          |
| `timeWindow` | object    | `{ start: "HH:mm", end: "HH:mm" }` within allowed days | `{ "start": "08:00", "end": "20:00" }` |

Schedule filtering happens early in the pipeline (inventory stage) and applies to both regular and mandatory offers. An offer outside its schedule window is excluded regardless of priority or mandatory status.

***

## Mandatory Governance

Marking an offer as `mandatory: true` activates a special runtime path for compliance-critical communications. Mandatory offers bypass decisioning gates, bypass suppression policies, and are lifted to the front of the ranking step regardless of their priority or model score. The lift is honored by all four ranking algorithms (`topN`, `diversity`, `round_robin`, `explore_exploit`) so the default `diversity` flow does not bury a mandatory regulatory notice in a low-traffic category.

### Required Fields

When `mandatory` is `true`, the API enforces three governance fields:

| Field                 | Purpose                                                                                        | Example                                              |
| --------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `mandatoryReason`     | Why this offer must be shown (audit trail)                                                     | `"CFPB regulation 2026-04 requires rate disclosure"` |
| `mandatoryApprovedBy` | User ID (UUID) of the approver — must reference an existing user in the same tenant            | `"6aceb4ae-992c-4d87-9a01-af2632d92cf9"`             |
| `mandatoryExpiresAt`  | ISO 8601 timestamp — must be a future date at create/update, and **enforced at decision time** | `"2026-04-15T23:59:59Z"`                             |

If any are missing, the API returns `400 Bad Request`. Only users with the `admin` role can create or update mandatory offers.

<Warning>
  Mandatory offers bypass decisioning gates and normal ranking. Use this only for regulatory or compliance-critical communications.
</Warning>

<Note>
  `mandatoryExpiresAt` is enforced **both at save time and at decision time**. It must be a future date when you save the offer, and once the timestamp passes, the decisioning runtime automatically treats the offer as **non-mandatory** — it falls back to normal eligibility/fit gating, contact policies, and ranking. You do not need to manually clear `mandatory`; the override self-retires at its expiry.
</Note>

### Example: Regulatory Disclosure

```json theme={null}
{
  "name": "CFPB Rate Disclosure Q2-2026",
  "status": "active",
  "categoryId": "cat_compliance",
  "priority": 90,
  "mandatory": true,
  "mandatoryReason": "CFPB regulation 2026-04 requires rate disclosure within 30 days",
  "mandatoryApprovedBy": "6aceb4ae-992c-4d87-9a01-af2632d92cf9",
  "mandatoryExpiresAt": "2026-04-15T23:59:59Z",
  "schedule": {
    "startDate": "2026-03-15T00:00:00Z",
    "endDate": "2026-04-15T23:59:59Z"
  }
}
```

During the 30-day window, this offer scores 1.0 and appears above all regular recommendations, bypasses propensity thresholds and segment requirements, still respects the daily mandatory cap (default 5 per customer), and automatically stops after April 15.

***

## Emergency Exclusions

The `emergencyExcluded` flag lets you instantly remove an offer from all recommendations without changing its status or modifying a Decision Flow. Designed for pricing errors, compliance issues, or product recalls.

| Aspect     | Behavior                                                            |
| ---------- | ------------------------------------------------------------------- |
| **Scope**  | Excluded from all flows, all channels, all segments                 |
| **Speed**  | Takes effect on the next Recommend API call — no cache flush needed |
| **Status** | Remains `active` (toggling back is a single API call)               |
| **Audit**  | Every toggle is recorded with timestamp, user, and previous value   |

**Set via API:**

```json theme={null}
PUT /api/v1/offers
{ "id": "offer_abc123", "emergencyExcluded": true }
```

**Set via UI:** Open the offer detail page and click the **Emergency Exclude** button.

<Warning>
  Emergency exclusion is a blunt instrument — it removes the offer from all channels, all placements, and all segments simultaneously. For targeted suppression (e.g., one channel only), use [Contact Policies](/decisioning/contact-policies) instead.
</Warning>

***

## Creating an Offer

<Steps>
  <Step title="Navigate to Offers">
    Go to **Studio > Offers** in the sidebar.
  </Step>

  <Step title="Click + New Offer">
    Click the **+ New Offer** button in the top-right corner.
  </Step>

  <Step title="Fill in basic info">
    Enter the offer name, description, and select a [Category](/studio/business-hierarchy) and sub-category.
  </Step>

  <Step title="Set priority and business value">
    Choose the priority level (Emphasis) and business value (Impact). Both default to 50.
  </Step>

  <Step title="Configure qualification (optional)">
    Set propensity thresholds, required segments, and recency rules.
  </Step>

  <Step title="Configure budget and schedule (optional)">
    Set impression caps, daily budget limits, start/end dates, and time windows.
  </Step>

  <Step title="Fill custom fields">
    Populate any custom fields defined by the parent category, including computed field formulas.
  </Step>

  <Step title="Save and activate">
    Save as draft, review, then set to `active` when ready.
  </Step>
</Steps>

***

## Field Reference

Every field accepted by `POST /api/v1/offers` (create) and `PUT /api/v1/offers` (update):

| Field                 | Required    | Type             | Default   | Description                                                                                                                                         |
| --------------------- | ----------- | ---------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                | Yes         | string           | --        | Display name. Must be unique within the tenant.                                                                                                     |
| `status`              | No          | enum             | `"draft"` | `draft`, `active`, `paused`, `archived`                                                                                                             |
| `categoryId`          | No          | string           | `null`    | Parent [Category](/studio/business-hierarchy) ID                                                                                                    |
| `subCategoryId`       | No          | string           | `null`    | Sub-category ID within the category                                                                                                                 |
| `productType`         | No          | string           | `""`      | Product nature: `bogo`, `discount`, `auto_insurance`, `credit_card`, etc. Used for PRIE strategy overrides and `offer_attribute` decisioning gates. |
| `priority`            | No          | integer (0--100) | `50`      | PRIE Emphasis (E) factor. Higher = more aggressive ranking.                                                                                         |
| `businessValue`       | No          | integer (0--100) | `50`      | PRIE Impact (I) factor. Overall business importance.                                                                                                |
| `margin`              | No          | float            | `0`       | Profit margin per conversion. Sub-weight of Impact (I) in PRIE scoring.                                                                             |
| `revenueValue`        | No          | float            | `0`       | Expected revenue per conversion. Sub-weight of Impact (I) in PRIE scoring.                                                                          |
| `mandatory`           | No          | boolean          | `false`   | Bypass qualification and rank above all regular offers                                                                                              |
| `mandatoryReason`     | Conditional | string           | `null`    | Required when `mandatory=true`                                                                                                                      |
| `mandatoryApprovedBy` | Conditional | string           | `null`    | Required when `mandatory=true`                                                                                                                      |
| `mandatoryExpiresAt`  | Conditional | string           | `null`    | Required when `mandatory=true`. ISO 8601.                                                                                                           |
| `description`         | No          | string           | `""`      | Internal documentation                                                                                                                              |
| `shortDesc`           | No          | string           | `""`      | Brief description surfaced in responses                                                                                                             |
| `eligibility`         | No          | object           | `{}`      | Qualification config (see above)                                                                                                                    |
| `budget`              | No          | object           | `{}`      | Budget config (see above)                                                                                                                           |
| `inventory`           | No          | object           | `{}`      | Inventory config — `totalStock` / `remainingStock` (see above)                                                                                      |
| `schedule`            | No          | object           | `{}`      | Schedule config (see above)                                                                                                                         |
| `tags`                | No          | string\[]        | `[]`      | Freeform tags for filtering                                                                                                                         |
| `metadata`            | No          | object           | `{}`      | Arbitrary key-value pairs for integrations                                                                                                          |
| `emergencyExcluded`   | No          | boolean          | `false`   | Instant exclusion from all recommendations                                                                                                          |

***

## API Quick Reference

### Create

```bash theme={null}
POST /api/v1/offers
```

```json theme={null}
{
  "name": "BOGO Iced Beverage",
  "shortDesc": "Buy one iced beverage, get one free this weekend",
  "categoryId": "cat_beverages",
  "priority": 70,
  "businessValue": 80,
  "status": "draft",
  "tags": ["bogo", "iced-beverage", "weekend"],
  "eligibility": {
    "propensityThreshold": 0.5,
    "recency": 7,
    "requiredSegments": ["rewards_member"]
  },
  "schedule": {
    "startDate": "2026-04-05T00:00:00Z",
    "endDate": "2026-04-07T23:59:59Z",
    "daysOfWeek": [5, 6, 0]
  }
}
```

**Response:** `201 Created` with the full offer object including generated `id`.

### List

```bash theme={null}
GET /api/v1/offers
```

Supports optional query parameters: `status`, `categoryId`, `tags`.

### Update

```bash theme={null}
PUT /api/v1/offers
```

Send `id` in the request body along with the fields to update. Only provided fields are changed.

### Delete

```bash theme={null}
DELETE /api/v1/offers?id={offerId}
```

Returns `409 Conflict` if the offer has creatives or interaction history. Add `?force=true` to confirm permanent deletion.

<Warning>
  Deleting an offer removes it permanently along with its creatives and interaction history. Consider archiving instead to preserve reporting integrity.
</Warning>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Channels" icon="paper-plane" href="/studio/channels">
    Define how your offers are delivered to customers.
  </Card>

  <Card title="Creatives" icon="palette" href="/studio/creatives">
    Create the content variants for each offer and channel.
  </Card>

  <Card title="Decision Flows" icon="diagram-project" href="/decisioning/decision-flows">
    Build the pipeline that scores, filters, and ranks your offers.
  </Card>

  <Card title="Glossary" icon="book" href="/reference/glossary">
    Look up key terms used across the platform.
  </Card>
</CardGroup>
