# Discount triggers

A **discount trigger** links one of your [saved segments](#api-segments) to a
discount code, then lets you read exactly **who qualifies for that code right
now**. Use it to run targeted offers: "20% off for repeat buyers", "a comeback
code for no-shows". All trigger endpoints require the `events:write` scope.

> **This is a read model, not an automation.** A trigger never emails anyone and
> never applies the code on its own. Unlike the first-party organizer panel —
> which can auto-send a code to a matching audience on a schedule — the API
> hands you the current match list and **you** deliver the code through your own
> channels (email, SMS, push, in-app). You stay in control of the message and
> the timing.

Because matches are evaluated **fresh on every call** against your live ticket
history, the list you read is always current — add a trigger once, then poll
`GET .../matches` whenever you're ready to send.

A serialized trigger object:

```json
{
  "id": "664f0a1b2c3d4e5f60718293",
  "discount_id": "664f0a1b2c3d4e5f60700001",
  "segment_id": "seg_1f2e3d4c5b6a",
  "active": true,
  "created_at": "2026-07-11T10:00:00.000Z"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | Trigger rule id — use it in every `.../triggers/{ruleId}` path. |
| `discount_id` | string | The discount code this trigger is attached to. |
| `segment_id` | string | The saved segment whose members qualify. |
| `active` | boolean | Whether the trigger is live. |
| `created_at` | string | Creation timestamp (ISO 8601). |

The endpoints:

| Method & path | Does |
| --- | --- |
| `POST /v1/discounts/{id}/triggers` | Link a saved segment to the discount code. |
| `GET /v1/discounts/{id}/triggers` | List the code's triggers. |
| `DELETE /v1/discounts/{id}/triggers/{ruleId}` | Remove a trigger. |
| `GET /v1/discounts/{id}/triggers/{ruleId}/matches` | List the customers who qualify **now**. |

---

## POST /v1/discounts/{id}/triggers

Link a saved segment to a discount code.

**Scope:** `events:write`

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Discount id. Unknown id returns `404 resource_missing`. |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `segment_id` | string | Yes | A saved segment you own. Missing returns `400 parameter_missing`; an id that doesn't resolve to one of your segments returns `400 parameter_invalid`. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/discounts/664f0a1b2c3d4e5f60700001/triggers \
  -H "Authorization: Bearer sk_test_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "segment_id": "seg_1f2e3d4c5b6a" }'
```

### Example response

`201 Created`

```json
{
  "id": "664f0a1b2c3d4e5f60718293",
  "discount_id": "664f0a1b2c3d4e5f60700001",
  "segment_id": "seg_1f2e3d4c5b6a",
  "active": true,
  "created_at": "2026-07-11T10:00:00.000Z"
}
```

---

## GET /v1/discounts/{id}/triggers

List the trigger rules attached to a discount code, newest first.

**Scope:** `events:write`

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Discount id. Unknown id returns `404 resource_missing`. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/discounts/664f0a1b2c3d4e5f60700001/triggers \
  -H "Authorization: Bearer sk_test_your_key_here"
```

### Example response

```json
{
  "object": "list",
  "has_more": false,
  "data": [
    { "id": "664f0a1b2c3d4e5f60718293", "discount_id": "664f0a1b2c3d4e5f60700001", "segment_id": "seg_1f2e3d4c5b6a", "active": true, "created_at": "2026-07-11T10:00:00.000Z" }
  ]
}
```

---

## DELETE /v1/discounts/{id}/triggers/{ruleId}

Remove a trigger from a discount code. The discount code itself is untouched.

**Scope:** `events:write`

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Discount id. |
| `ruleId` | string | Yes | Trigger rule id. Unknown id returns `404 resource_missing`. |

### Example request

```bash
curl https://api.ticketconnect.example/v1/discounts/664f0a1b2c3d4e5f60700001/triggers/664f0a1b2c3d4e5f60718293 \
  -H "Authorization: Bearer sk_test_your_key_here" \
  -X DELETE
```

### Example response

```json
{ "id": "664f0a1b2c3d4e5f60718293", "deleted": true }
```

---

## GET /v1/discounts/{id}/triggers/{ruleId}/matches

Return the customers who qualify for this code **right now**. The trigger's
linked segment is re-evaluated against your live ticket history on every call —
so this list is exactly who you'd send the code to today. Deliver the code to
them through your own channels.

**Scope:** `events:write`

Each row is a segment member: `customer_id`, `email`, the one kind-relevant
count (`purchase_count` | `attended_event_count` | `no_show_count`), and
`last_activity_at`. Results are identical to what
[`GET /v1/segments/{id}/members`](#api-segments) returns for the linked
segment — the two share the same audience engine.

### Path params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Discount id. |
| `ruleId` | string | Yes | Trigger rule id. Unknown id returns `404 resource_missing`. |

### Query params

| Param | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | integer | No | Page size, `1`–`100` (default `20`). |
| `starting_after` | string | No | Cursor — the last `customer_id` you saw on the previous page. |

### Example request

```bash
curl "https://api.ticketconnect.example/v1/discounts/664f0a1b2c3d4e5f60700001/triggers/664f0a1b2c3d4e5f60718293/matches?limit=2" \
  -H "Authorization: Bearer sk_test_your_key_here"
```

### Example response

```json
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "customer_id": "cus_ext_42",
      "email": "dana@example.com",
      "purchase_count": 5,
      "last_activity_at": "2026-07-02T21:14:00.000Z"
    }
  ]
}
```

> **Note:** If the linked segment has since been deleted, `matches` returns an
> empty list rather than an error — the trigger simply has no one to match.
