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

# Triggers

> Define event-driven trigger rules that fire actions (journey enrollment, webhook, decision request) based on incoming event conditions.

<Note>
  **See also**: [Triggers concept and configuration](/studio/triggers) for what this API powers, when to call it, and how it is configured.
</Note>

## GET /api/v1/triggers

List all trigger rules sorted by priority descending.

### Query Parameters

| Parameter   | Type    | Default | Description           |
| ----------- | ------- | ------- | --------------------- |
| `status`    | string  | —       | Filter by status      |
| `eventType` | string  | —       | Filter by event type  |
| `limit`     | integer | `50`    | Max results (max 200) |
| `offset`    | integer | `0`     | Pagination offset     |

### Response

```json theme={null}
{
  "data": [
    {
      "id": "trigger_001",
      "name": "High-Value Purchase Trigger",
      "eventType": "purchase",
      "actionType": "enroll_journey",
      "actionConfig": { "journeyId": "journey_001" },
      "condition": { "field": "amount", "operator": "gt", "value": 500 },
      "priority": 80,
      "cooldownMs": 86400000,
      "status": "active",
      "createdAt": "2026-02-20T10:00:00.000Z"
    }
  ],
  "pagination": { "total": 12, "limit": 50, "offset": 0, "hasMore": false }
}
```

***

## POST /api/v1/triggers

Create a new trigger rule.

### Request Body

| Field          | Type    | Required | Description                                                                                                                                                                                                                                                                                                                        |
| -------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`         | string  | Yes      | Trigger name                                                                                                                                                                                                                                                                                                                       |
| `eventType`    | string  | Yes      | Event type to match. Built-in events emitted by the platform: `outcome.recorded`, `customer.created`, `customer.updated`, `segment.entered`, `segment.exited`, `journey.completed`, `offer.expired`, `budget.exhausted`. Any non-empty string is accepted so you can match custom events emitted through the capture/respond APIs. |
| `actionType`   | string  | Yes      | Action to fire: `"enroll_journey"`, `"fire_recommend"`, `"webhook"`, `"update_attribute"`                                                                                                                                                                                                                                          |
| `actionConfig` | object  | No       | Action-specific configuration                                                                                                                                                                                                                                                                                                      |
| `condition`    | object  | No       | Condition to evaluate against the event payload (see below).                                                                                                                                                                                                                                                                       |
| `description`  | string  | No       | Description                                                                                                                                                                                                                                                                                                                        |
| `priority`     | integer | No       | Priority (0-100, higher = evaluated first). Default: `50`                                                                                                                                                                                                                                                                          |
| `cooldownMs`   | integer | No       | Cooldown period in milliseconds before the trigger can fire again for the same customer. Default: `0`                                                                                                                                                                                                                              |

<Note>
  **Condition shape.** A leaf rule is `{ "field": "<payloadKey>", "operator": "<op>", "value": <any> }`; rules combine into groups with `all` (AND) or `any` (OR) — e.g. `{ "all": [ { "field": "amount", "operator": "gt", "value": 500 }, { "any": [ ... ] } ] }`. Operators: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in` (value must be an array), `exists`. An unknown operator fails closed (the rule does not match). An empty or omitted `condition` always fires.
</Note>

### Example

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/triggers \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: my-tenant" \
  -d '{
    "name": "High-Value Purchase Trigger",
    "eventType": "purchase",
    "actionType": "enroll_journey",
    "actionConfig": { "journeyId": "journey_001" },
    "condition": { "field": "amount", "operator": "gt", "value": 500 },
    "priority": 80,
    "cooldownMs": 86400000
  }'
```

**Response:** `201 Created`

***

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

Get trigger rule details.

***

## PUT /api/v1/triggers/{id}

Update a trigger rule. Only provided fields are updated.

| Field          | Type    | Description                                            |
| -------------- | ------- | ------------------------------------------------------ |
| `name`         | string  | Updated name                                           |
| `description`  | string  | Updated description                                    |
| `eventType`    | string  | Updated event type                                     |
| `condition`    | object  | Updated condition                                      |
| `actionType`   | string  | Updated action type                                    |
| `actionConfig` | object  | Updated action config                                  |
| `cooldownMs`   | integer | Updated cooldown                                       |
| `priority`     | integer | Updated priority                                       |
| `status`       | string  | Updated status. One of `active`, `paused`, `disabled`. |

**Response:** `200 OK`

***

## DELETE /api/v1/triggers/{id}

Delete a trigger rule. Requires admin role.

**Response:** `204 No Content`

***

## POST /api/v1/triggers/{id}/test

Dry-run a trigger against a sample event payload. Returns whether the trigger would fire without actually executing the action.

### Request Body

| Field          | Type   | Required | Description                                               |
| -------------- | ------ | -------- | --------------------------------------------------------- |
| `eventPayload` | object | Yes      | Sample event data to test against the trigger's condition |

### Response

```json theme={null}
{
  "wouldFire": true,
  "reason": "All conditions matched"
}
```

```json theme={null}
{
  "wouldFire": false,
  "reason": "Condition did not match the provided payload"
}
```

***

## POST /api/v1/triggers/file-arrival

Push-based webhook that fires a data pipeline when a file lands. Unlike the trigger-rule
endpoints above, this is a **system webhook**: it is not RBAC-gated — it self-authorizes with the
shared `CRON_SECRET`. Rate limited to 60 requests / minute per source IP. Returns `503` if
`CRON_SECRET` is not configured.

### Authentication

One of:

* `Authorization: Bearer <CRON_SECRET>` header
* `X-Cron-Secret: <CRON_SECRET>` header
* `secret` field in the request body (per-pipeline override; takes precedence)
* A same-origin request carrying a valid session cookie (the editor's "Fire now" button)

### Request body

Two shapes are accepted:

```json theme={null}
// 1. Native
{ "pipelineId": "pl_...", "tenantId": "my-tenant", "objectKey": "inbox/orders-2026-07-03.csv", "secret": "<optional>" }
```

```json theme={null}
// 2. S3 → EventBridge envelope
{ "detail": { "bucket": { "name": "my-bucket" }, "object": { "key": "inbox/orders-2026-07-03.csv" } } }
```

For the EventBridge shape the route matches the `(bucket, key)` pair against every active
`irVersion: "1.0"` pipeline that has a `file_arrival` trigger, and probes the source's pattern to
confirm the key really matches before dispatching.

### Behavior

The route confirms the resolved pipeline actually has a `file_arrival` trigger and belongs to the
asserted tenant, writes a `PipelineRun` row (`triggerSource: "file_arrival"`), then dispatches the
run **in-process** (fire-and-forget). Poll [`GET /api/v1/pipelines/{id}/runs`](./pipeline-runs) for
the outcome.

<Note>
  **Push and poll can't double-load the same file.** A pipeline may have both this
  webhook and the [in-process scheduler](/data/pipelines/flow-scheduler) polling the
  same inbox. Both paths claim the run under a transaction-scoped advisory lock keyed
  on `(tenant, pipeline, objectKey)` and stamp `metadata.objectKey` on the run row; a
  claim first checks for an in-flight run with the same `objectKey`. So for a given
  object, whichever path fires first wins and the other is a no-op — the file is loaded
  exactly once regardless of how many triggers observe it.
</Note>

### Response `202`

```json theme={null}
{
  "ok": true,
  "accepted": true,
  "pipelineId": "pl_...",
  "tenantId": "my-tenant",
  "objectKey": "inbox/orders-2026-07-03.csv",
  "runId": "run_..."
}
```

### Errors

| Code  | Reason                                                                                                            |
| ----- | ----------------------------------------------------------------------------------------------------------------- |
| `400` | Invalid JSON, or the payload is neither a native nor an EventBridge envelope.                                     |
| `401` | Missing or invalid secret (and not a trusted same-origin session).                                                |
| `404` | No pipeline matched the `(bucket, key)`, the pipeline has no published IR, or the asserted tenant doesn't own it. |
| `503` | `CRON_SECRET` not configured — the endpoint is disabled.                                                          |

***

## POST /api/v1/triggers/eventbridge

Direct receiver for AWS EventBridge API Destinations that proxies to a `/api/v1/cron/*` endpoint —
an alternative to the Terraform Lambda hop for operators who prefer fewer moving parts. Not
RBAC-gated; authenticated with `CRON_SECRET` / `CRON_TOKEN`.

### Authentication

* **HMAC** — `X-EventBridge-Signature: <hex>` = `HMAC-SHA256(CRON_SECRET, rawBody)` (preferred), or
* **Shared secret** — `X-Cron-Token`, `X-Cron-Secret`, or `Authorization: Bearer` equal to the secret.

Returns `401` (no body detail) when the secret is unset or verification fails.

### Request body

```json theme={null}
{
  "schedule_name": "fairness_recheck",
  "cron_endpoint_path": "/api/v1/cron/fairness-recheck"
}
```

`cron_endpoint_path` must be in the route's allowlist (the `/api/v1/cron/*` jobs plus the legacy
`/api/cron/tick`); anything else returns `400`. On a match the route self-calls that cron endpoint
(GET, forwarding the secret) and returns its result.

### Response

```json theme={null}
{
  "ok": true,
  "scheduleName": "fairness_recheck",
  "path": "/api/v1/cron/fairness-recheck",
  "downstreamStatus": 200,
  "downstream": { "...": "the cron job's response" }
}
```

The HTTP status mirrors the downstream cron response. `400` = path not in allowlist, `502` = the
downstream self-call failed.

***

## Roles

The trigger-rule endpoints are RBAC-gated:

| Endpoint                   | Allowed Roles |
| -------------------------- | ------------- |
| `GET /triggers`            | admin, editor |
| `POST /triggers`           | admin, editor |
| `PUT /triggers/{id}`       | admin, editor |
| `DELETE /triggers/{id}`    | admin         |
| `POST /triggers/{id}/test` | admin, editor |

The `file-arrival` and `eventbridge` webhooks are **not** RBAC-gated — they self-authorize with
`CRON_SECRET` as described above.

See also: [Triggers](/studio/triggers)
