# Product Rules

Product rules enable dynamic, event-driven offer flows including upsells, cross-sells, second chance offers, and complex multi-product configurations. This structure provides partners with a powerful, declarative way to define product relationships and conditional visibility rules.

### Structure

The `product_rules` array is returned in the Create Offer response and defines how products should be displayed, grouped, and made interactive based on user actions.

#### Root Object

```json
{
  "product_rules": [
    {
      "slug": "group_id",
      "product_ids": ["product_id_1"],
      "initial_state": "show",
      "min_select": 1,
      "max_select": 1,
      "events": []
    }
  ]
}
```

#### Field Definitions

**Group Object**

| Field           | Type           | Required | Description                                                                            |
| --------------- | -------------- | -------- | -------------------------------------------------------------------------------------- |
| `slug`          | string         | Yes      | Unique slug for the ProductRules. Used for event targeting and reference in UI/booking |
| `product_ids`   | array\<string> | Yes      | Array of product/quote IDs included in this group                                      |
| `initial_state` | enum           | Yes      | Initial visibility/interaction state: `"show"`, `"hide"`, or `"disable"`               |
| `min_select`    | integer        | Yes      | Minimum number of products the customer must select from this group (0 = optional)     |
| `max_select`    | integer        | Yes      | Maximum number of products the customer can select from this group                     |
| `events`        | array\<Event>  | No       | Array of event-driven rules. Can be empty or omitted for terminal groups               |

**Event Object**

| Field         | Type   | Required | Description                                                                                |
| ------------- | ------ | -------- | ------------------------------------------------------------------------------------------ |
| `on`          | enum   | Yes      | User action that triggers this event: `"select"`, `"deselect"`, `"accept"`, or `"decline"` |
| `action`      | enum   | Yes      | Action to perform on target group: `"show"`, `"hide"`, `"enable"`, or `"disable"`          |
| `target_rule` | string | Yes      | ProductRules Slug to apply action to (must reference existing ProductRules)                |

**Event Triggers**

| Trigger    | When It Fires                                | Use Case                                             |
| ---------- | -------------------------------------------- | ---------------------------------------------------- |
| `select`   | User selects product(s) meeting `min_select` | Show upsells immediately when user picks a product   |
| `deselect` | User's selection drops below `min_select`    | Hide/cleanup dependent groups when selection removed |
| `accept`   | User explicitly accepts the group            | Show next step after accepting Offer                 |
| `decline`  | User explicitly declines the entire group    | Show second chance Offer when user opts out          |

**Event Actions**

| Action    | Effect                                  | Use Case                                   |
| --------- | --------------------------------------- | ------------------------------------------ |
| `show`    | Make group visible and interactive      | Reveal upsell after main product selected  |
| `hide`    | Make group invisible                    | Hide alternative when user makes selection |
| `enable`  | Make group interactive (if disabled)    | Unlock addon after main product accepted   |
| `disable` | Make group non-interactive (grayed out) | Disable competing option in XOR scenario   |

**Initial State**

| State     | Description                               | Use Case                                  |
| --------- | ----------------------------------------- | ----------------------------------------- |
| `show`    | Visible and interactive from start        | Primary Offer, always available options   |
| `hide`    | Not visible until triggered by event      | Upsells, second chance Offer              |
| `disable` | Visible but non-interactive until enabled | Show addon but require main product first |

### Common use cases

#### Single Product

Simple single product Offer with no events.

```json
{
  "product_rules": [
    {
      "slug": "primary",
      "product_ids": ["product_A"],
      "initial_state": "show",
      "min_select": 1,
      "max_select": 1,
      "events": []
    }
  ]
}
```

**Use Case:** Standard single product offer where user must select the product to proceed.

**Implementation:**

* Display the product immediately
* User must select exactly 1 product
* No conditional behavior

***

#### Multiple Products (XOR - One-of)

User must pick exactly one of multiple products.

```json
{
  "product_rules": [
    {
      "slug": "plan_options",
      "product_ids": ["product_A", "product_B"],
      "initial_state": "show",
      "min_select": 1,
      "max_select": 1,
      "events": []
    }
  ]
}
```

**Use Case:** Tiered pricing where user selects one plan from multiple options (e.g., Basic vs Premium, Bronze vs Silver vs Gold).

**Implementation:**

* Display all products in the group simultaneously
* User must select exactly 1 product
* Typically rendered as radio buttons or tabs

***

#### Multiple Product Rules (Second Chance Offer)

Show alternative product when user declines primary offer.

```json
{
  "product_rules": [
    {
      "slug": "primary",
      "product_ids": ["premium_product"],
      "initial_state": "show",
      "min_select": 1,
      "max_select": 1,
      "events": [
        {
          "on": "decline",
          "action": "show",
          "target_rule": "second_chance"
        },
        {
          "on": "select",
          "action": "hide",
          "target_rule": "second_chance"
        }
      ]
    },
    {
      "slug": "second_chance",
      "product_ids": ["budget_product"],
      "initial_state": "hide",
      "min_select": 1,
      "max_select": 1,
      "events": []
    }
  ]
}
```

**Use Case:** Offer a lower-priced alternative when user declines the primary offer to maximize conversion.

**Flow:**

1. User sees primary offer (premium product at higher price)
2. User clicks "No thanks" → `decline` event fires
3. Primary group hides, second chance group shows (budget product at lower price)
4. If user changes mind and selects primary → second chance hides

**Example with Real Data:**

{% code overflow="wrap" expandable="true" %}

```json
{
  "id": "offer_123",
  "currency": "AUD",
  "products": [
    {
      "id": "quote_id_1",
      "type": "insurance",
      "details": {
        "policy_start_date": "2025-10-29T10:00:00+00:00",
        "policy_end_date": "2025-11-28T10:00:00+00:00",
        "finance": {
          "price": {
            "total_amount": 16,
            "total_amount_formatted": "A$16.00"
          }
        },
        "pds_url": "https://www.xcover.com/en/pds/..."
      }
    },
    {
      "id": "quote_id_2",
      "type": "insurance",
      "details": {
        "policy_start_date": "2025-10-29T10:00:00+00:00",
        "policy_end_date": "2025-11-28T10:00:00+00:00",
        "finance": {
          "price": {
            "total_amount": 10,
            "total_amount_formatted": "A$10.00"
          }
        },
        "pds_url": "https://www.xcover.com/en/pds/..."
      }
    }
  ],
  "product_rules": [
    {
      "slug": "primary",
      "product_ids": ["quote_id_1"],
      "initial_state": "show",
      "min_select": 1,
      "max_select": 1,
      "events": [
        {
          "on": "decline",
          "action": "show",
          "target_rule": "second_chance"
        },
        {
          "on": "select",
          "action": "hide",
          "target_rule": "second_chance"
        }
      ]
    },
    {
      "slug": "second_chance",
      "product_ids": ["quote_id_2"],
      "initial_state": "hide",
      "min_select": 1,
      "max_select": 1,
      "events": []
    }
  ],
  "content": {
    "heading": "Protect Your Trip",
    "positive_cta": "Add to booking",
    "negative_cta": "No thanks",
    "extras": {
        "second_chance_heading": "Your last chance!",
        "second_chance_subheading": "Don't leave empty-handed—grab this special deal."
    },
    "products": [
      {
        "id": "quote_id_1",
        "title": "Essential Travel Protection",
        "benefits": {
          "benefit_1": "Up to $2,000,000 in emergency medical expenses",
          "benefit_2": "Cover for unexpected trip disruptions"
        },
        "credibility_badge": "Most Popular"
      },
      {
        "id": "quote_id_2",
        "title": "Cancel For Any Reason",
        "benefits": {
          "benefit_1": "Receive up to 75% of your trip costs back",
          "benefit_2": "Cancel for reasons not covered by standard policy"
        }
      }
    ]
  }
}
```

{% endcode %}

### Business Rules & Validation

#### Event Processing Rules

1. **Events fire only on user interactions**, not on programmatic state changes from other events
2. **Events are processed synchronously** in the order they appear in the `events` array
3. **State changes are idempotent** - showing a visible group or hiding a hidden group has no effect
4. **Cleanup must be explicit** - system does not auto-reverse actions; partners must configure bidirectional events

### Best Practices

1. **Use `initial_state: "show"` for primary offers** - Always display the main offer immediately
2. **Use `initial_state: "hide"` for secondary offers** - Hide upsells, cross-sells, and second chance offers until triggered
3. **Configure bidirectional events** - If `select` shows a group, `deselect` should hide it
4. **Provide rule-specific content** - Use `content.extras` to customize messaging for second chance offers
5. **Set appropriate min/max values** - Use `min_select: 1, max_select: 1` for required single selection
6. **Handle opt-out** - Record opt-out when user declines all visible groups


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://partner-docs.covergenius.com/offers/guides/product-groups-and-rules.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
