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

# Approvals

> Enterprise governance approval workflow for production changes. Supports submit, approve, and reject flows with automatic entity application on approval.

## GET /api/v1/approvals

List approval requests filtered by status.

### Query Parameters

| Parameter | Type   | Default     | Description                                               |
| --------- | ------ | ----------- | --------------------------------------------------------- |
| `status`  | string | `"pending"` | Filter by status: `"pending"`, `"approved"`, `"rejected"` |

### Response

```json theme={null}
[
  {
    "id": "approval_001",
    "entityType": "qualificationRule",
    "entityId": "qr_001",
    "entityName": "Credit Score Rule",
    "action": "update",
    "status": "pending",
    "requesterId": "user_001",
    "requesterName": "John Doe",
    "payload": { "config": { "threshold": 650 } },
    "createdAt": "2026-03-15T10:00:00.000Z"
  }
]
```

***

## POST /api/v1/approvals

Create a new approval request.

### Request Body

| Field        | Type                                 | Required | Description                                                                                                                                                                                                                                                                        |
| ------------ | ------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `entityType` | string                               | Yes      | Entity type (e.g., `"qualificationRule"`, `"contactPolicy"`, `"decisionFlow"`, `"offer"`)                                                                                                                                                                                          |
| `entityId`   | string                               | Yes      | ID of the entity to modify                                                                                                                                                                                                                                                         |
| `entityName` | string                               | Yes      | Display name of the entity                                                                                                                                                                                                                                                         |
| `action`     | string                               | Yes      | `"create"`, `"update"`, or `"delete"`                                                                                                                                                                                                                                              |
| `payload`    | object                               | No       | The proposed changes to apply on approval                                                                                                                                                                                                                                          |
| `stages`     | `Array<{ stageName, requiredRole }>` | No       | Optional multi-stage chain. Each stage has `stageName: string` and `requiredRole: "viewer" \| "editor" \| "admin"`. Decisions walk the stages in order. When omitted, one default `admin` stage is created. See [Four-eyes governance](/governance-security/governance-four-eyes). |

### Example

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/approvals \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: my-tenant" \
  -d '{
    "entityType": "qualificationRule",
    "entityId": "qr_001",
    "entityName": "Credit Score Rule",
    "action": "update",
    "payload": { "config": { "threshold": 650 } }
  }'
```

**Response:** `201 Created`

***

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

Get a single approval request.

***

## POST /api/v1/approvals/{id}

Resolve an approval request (approve or reject). Editors and admins
can decide; the per-stage `requiredRole` is then enforced inside the
walk. Viewers are 403'd at the route boundary.

When the **last** stage of the chain is approved, the stored payload
is automatically applied to the target entity. Supported entity types
for auto-application: `algorithmModel`, `qualificationRule`,
`contactPolicy`, `decisionFlow`, `offer`, `creative`, `channel`,
`rankingProfile`, `ai_recommendation`. An unsupported `entityType` causes
the whole transaction (stage update + parent flip + apply) to roll
back — returning a 409 — so an approval cannot flip to `approved`
without the entity actually changing.

<Note>
  `ai_recommendation` is a special case: it's how the [Decisioning
  Autopilot](/ai-ml/autopilot)'s `auto_gated` mode routes AI proposals
  through four-eyes review. Approving it re-fetches the live
  `AiRecommendation` row (the approval's stored `payload` doesn't carry
  `type`) and applies it through the same shared executor used by
  `POST /api/v1/ai/recommendations/{id}/apply` — not a raw field update.
  Idempotent: re-resolving an already-`applied` recommendation is a no-op
  rather than a duplicate apply.
</Note>

### Request Body

| Field      | Type   | Required | Description               |
| ---------- | ------ | -------- | ------------------------- |
| `action`   | string | Yes      | `"approve"` or `"reject"` |
| `comments` | string | No       | Reviewer comments         |

### Response (approved)

```json theme={null}
{
  "id": "approval_001",
  "status": "approved",
  "approverId": "user_002",
  "approverName": "Admin",
  "comments": "Looks good, deploying to production",
  "resolvedAt": "2026-03-16T14:30:00.000Z"
}
```

### Response (rejected)

```json theme={null}
{
  "id": "approval_001",
  "status": "rejected",
  "approverId": "user_002",
  "approverName": "Admin",
  "comments": "Threshold too aggressive, please revise",
  "resolvedAt": "2026-03-16T14:30:00.000Z"
}
```

***

## Roles

| Endpoint                         | Allowed Roles                                                                 |
| -------------------------------- | ----------------------------------------------------------------------------- |
| `GET /approvals`                 | any authenticated                                                             |
| `POST /approvals`                | any authenticated                                                             |
| `GET /approvals/{id}`            | any authenticated (returns parent + ordered `stages` array in one round-trip) |
| `POST /approvals/{id}` (resolve) | `editor` or `admin` (per-stage `requiredRole` is enforced inside the walk)    |

## Auto-expiry

Pending approvals that sit unresolved for longer than
`APPROVAL_MAX_AGE_HOURS` (default `168` = 7 days) are auto-flipped to
`status = "expired"` by the
[`/api/v1/cron/approvals-expire`](./cron#get-apiv1cronapprovals-expire)
endpoint. Wire it into your scheduler at any cadence — every 15 minutes
is plenty since the operation is a single bulk update and idempotent.

The cutoff is configurable per-deployment via the
`APPROVAL_MAX_AGE_HOURS` env var. Expired requests retain their full
audit trail; only `status` and `resolvedAt` change.

## Four-eyes decision-flow publish gate

When a tenant sets `requirePublishApproval: true`
([tenant settings](/api-reference/tenant-settings)), publishing a decision flow
requires a dedicated approval request:

| Field        | Value                                |
| ------------ | ------------------------------------ |
| `entityType` | `"decisionFlow"`                     |
| `action`     | `"publish"`                          |
| `entityId`   | the decision-flow id being published |

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/approvals \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: my-tenant" \
  -d '{
    "entityType": "decisionFlow",
    "entityId": "df_001",
    "entityName": "Spring Campaign Flow",
    "action": "publish"
  }'
```

Unlike the auto-applied entity types above, a `publish` approval does **not**
mutate the flow when it is approved — it simply records the second-person
sign-off. The [publish route](/api-reference/decision-flows) checks for the
latest approved request matching this shape, and one approval authorizes exactly
one publish (the publish stamps the approval's id onto the new version so it
can't be reused). Because stage walking rejects self-approval and duplicate
approvers, an approved `publish` request guarantees two distinct identities.

See also: [Compliance](/governance-security/compliance) · [Cron jobs](./cron)
