> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.kota.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.kota.io/_mcp/server.

# Employee Enrolment

> Enrol an employee into a group's health insurance policy using Kota's Enrolment Intent API

This guide walks you through enrolling an individual employee into a group's health
insurance using **Enrolment Intents**. You'll learn how to create an intent, follow it
through its lifecycle, handle the states that need your input, and confirm enrolment.

> **Info**
>
> Before starting, ensure you've completed:
>
> * [Authentication](/core-components/authentication): API key setup and idempotency
> * [Employer and employee management](/core-components/employer-employee-management): syncing employers and employees
> * [Webhooks and events](/core-components/webhooks-and-events): receiving asynchronous updates
>
> This guide assumes the employer already has an **active group policy** and the employee
> has been [assigned to the group](/api/employee-flow/assign-employee-to-group). See the
> [Employer Health Insurance Setup](/api/employer-health-insurance) guide to create the
> group policy first.

---

## Overview

An **enrolment intent** represents the intention to enrol one employee into one group,
and it tracks the status and progress of that enrolment from start to finish. It follows
the same intent-based pattern as the rest of the Kota API: you create the intent, then
react as Kota processes it asynchronously and advances it through a series of statuses.

Most of the work happens in the background. After you create an intent, Kota evaluates
eligibility, talks to the insurance provider, and moves the intent through its lifecycle.
Your integration reacts to those changes, either by subscribing to
[webhooks](/core-components/webhooks-and-events) or by polling the intent.

> **Note**
>
> Enrolment intents apply only to **manual-enrolment** groups. For API-only integrations,
> manual-enrolment groups should usually be your default choice because your integration
> controls when each employee is added to the group and enrolled.
>
> Use automatic enrolment groups only when you are integrated with Embedded or Hosted and want Kota to automatically enrol all
> employees for a particular employer. For automatic enrolment groups, Kota handles the
> group addition and in some cases enrolment flows, so you don't create enrolment intents yourself.

### Lifecycle

```mermaid
flowchart TD
  create[Create enrolment intent]
  ready{Employee active?}
  scheduled[scheduled]
  processing[processing]
  ineligible[ineligible<br />terminal]
  action_required[action_required]
  coverage_options_required[coverage_options_required]
  pending_confirmation[pending_confirmation]
  not_undertaken[not_undertaken]
  enrolling[enrolling]
  enrolled[enrolled]

  create --> ready
  ready -- No --> scheduled
  ready -- Yes --> processing
  scheduled --> processing
  processing --> ineligible
  processing --> action_required
  processing --> coverage_options_required
  processing --> pending_confirmation
  action_required -- Resolved --> enrolling
  action_required -- Rejected --> not_undertaken
  coverage_options_required -- Selections submitted --> pending_confirmation
  coverage_options_required -- Rejected --> not_undertaken
  pending_confirmation -- Confirm --> enrolling
  pending_confirmation -- Rejected --> not_undertaken
  enrolling --> enrolled
```

`Create enrolment intent` represents the API call, not an observable status. After
creation, an intent generally starts in `processing`, unless the employee is not active
yet, in which case it starts in `scheduled`.

For this flow, the employee is ready once their employee status is `active` and they have
already been added to the group you are trying to enrol them into. Group membership is a
creation precondition: if the employee has not been added to the group, create the group
assignment first.

### Statuses

The `status` field is the source of truth for where an intent is. Drive your integration
off it.

| Status                      | Meaning                                                                                                  | What you do                                        |
| --------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| `processing`                | Kota is evaluating eligibility and preparing the enrolment.                                              | Wait. Transient.                                   |
| `scheduled`                 | The employee isn't active yet (e.g. future start date). Picked up automatically once they become active. | Waiting for employee to be Active.                 |
| `ineligible`                | The employee can't be enrolled. See `ineligibility_reason`.                                              | Resolve the cause, then create a new intent.       |
| `action_required`           | More information is needed before enrolment can proceed. See `action_required`.                          | Collect and submit the required information.       |
| `coverage_options_required` | Policy-scoped coverage options must be selected before enrolment can proceed.                            | Submit coverage selections (or reject the intent). |
| `pending_confirmation`      | Enrolment is ready but needs explicit confirmation (opt-in benefit, or `force_confirmation`).            | Confirm (or reject) the intent.                    |
| `enrolling`                 | Confirmed. Kota is enrolling the employee with the provider.                                             | Wait. Transient.                                   |
| `enrolled`                  | The policy has been issued. `policy_enrolments[].id` is populated.                                       | Done.                                              |
| `not_undertaken`            | The intent was rejected and will not proceed.                                                            | Terminal. Create a new intent to retry.            |

> **Note**
>
> Status values, ineligibility reasons, and action codes are returned in `snake_case`. New
> values may be added as Kota onboards providers, so handle unknown enum values gracefully
> and prefer the human-readable `reason` fields when displaying information to end users.

---

## Step 1: Create an enrolment intent

Create one intent per employee, per group. Only `employee_id` and `group_id` are
required.

**Endpoint:** [`POST /enrolment_intents`](/api-reference/enrolment-intents/create-enrolment-intent)

**Request body:**

| Field                  | Type    | Required | Description                                                                                                                                                                                                   |
| ---------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `employee_id`          | string  | Yes      | The employee to enrol (prefixed `ee_`).                                                                                                                                                                       |
| `group_id`             | string  | Yes      | The group to enrol them into (prefixed `gr_`).                                                                                                                                                                |
| `force_confirmation`   | boolean | No       | Force the intent through `pending_confirmation` even when the provider would not otherwise require explicit confirmation. Defaults to `false`.                                                                |
| `policy_configuration` | object  | No       | Overrides the group defaults for this intent. Use `desired_policy_start_date` for the requested coverage start date, and `enrolment_date` for the date the employee or employer made the enrolment selection. |
| `coverage_selections`  | array   | No       | Optional policy-scoped coverage option selections. If provided and valid, the `coverage_options_required` step is skipped.                                                                                    |

`force_confirmation` is a rare option. Use it when your integration needs to guarantee
that the employee explicitly chooses whether to proceed before Kota enrols them. Each
provider has its own rules for when confirmation is required, and `force_confirmation`
lets you add this decision step even if the provider would normally allow enrolment to
continue without it.

`coverage_selections` is optional at create time. Use it when you already know the
employee's policy-scoped coverage choices and want to skip the
`coverage_options_required` status. Otherwise, wait for that status and submit
selections later with the coverage-selections endpoint.

`desired_policy_start_date` and `enrolment_date` answer different questions:

* `desired_policy_start_date` is the requested date for the policy to start. This date is
  subject to the employee's employment date, the enrolment date, and provider-specific
  restrictions, so it is a preference rather than a guarantee.
* `enrolment_date` is the date the enrolment selection was made. Set it when the employee
  or employer chose enrolment before your service submitted the intent to Kota, for
  example if there was a delay between the choice being made and your API call.

**`cURL`**

```bash cURL
curl -X POST "https://api.kota.io/enrolment_intents" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c9b2a-3d4e-4f50-8a1b-2c3d4e5f6071" \
  -d '{
    "employee_id": "ee_2b9c4d6e8f0a1b3c5d7e9f0a1b2c3d4e",
    "group_id": "gr_a1b2c3d4e5f60718293a4b5c6d7e8f90",
    "force_confirmation": false,
    "policy_configuration": {
      "desired_policy_start_date": "2026-07-01"
    }
  }'
```

**Response:**

```json
{
  "object": "enrolment_intent",
  "id": "ei_8f3a1c20d4e9457bb1f0a9c2e7d61234",
  "employee_id": "ee_2b9c4d6e8f0a1b3c5d7e9f0a1b2c3d4e",
  "group_id": "gr_a1b2c3d4e5f60718293a4b5c6d7e8f90",
  "status": "processing",
  "force_confirmation": false,
  "policy_enrolments": [
    {
      "type": "health_insurance",
      "estimated_effective_from": "2026-07-01",
      "provider": { "id": "pr_allianz", "name": "Allianz" },
      "plan": { "id": "pl_xyz789", "name": "Health Plan – Level 2", "currency": "EUR" },
      "id": null
    }
  ],
  "ineligibility_reason": null,
  "action_required": null,
  "pending_confirmation": null,
  "disclosures": []
}
```

> **Info**
>
> Send an `Idempotency-Key` on this `POST`. Retries with the same key won't create a
> duplicate intent, which is useful since creation kicks off asynchronous work. See
> [Idempotency](/core-components/authentication#idempotent-requests).

**Implementation notes:**

The request is rejected with a `400`/`404` if a precondition isn't met. Make sure that:

* the group exists, is `ready`, and uses **manual** enrolment;
* the employee exists under the same employer and has been **added to the group**;
* the employee is **eligible** for the group;
* there is **no in-flight intent** already for this employee + group (an intent counts as in-flight unless it's `ineligible` or `not_undertaken`).

You do **not** need the employee to be active yet. If their start date is in the future,
the intent is created in `scheduled` status and processed automatically once they become
active. See [API Errors](/api-reference/errors) for the error response shape.

---

## Step 2: Track progress

After creation the intent advances on its own. **Use webhooks to react to status
changes. Don't poll on a timer or assume how long a step takes.**

> **Warning**
>
> How long an intent takes to move between statuses varies and is largely outside your
> control: eligibility checks, provider response times, and policy issuance can each take
> anywhere from seconds to several days depending on the insurer. Treat every transition as
> an asynchronous event. Subscribe to webhooks and act when they arrive, rather than
> polling on a fixed interval or building in time-based assumptions, which will either
> hammer the API or act on stale state. Reserve the retrieve/list endpoints for
> reconciliation and recovery (e.g. backfilling a missed webhook), not as your primary
> signal.

### Webhooks (recommended)

Subscribe to the enrolment-intent events. Kota emits one per status transition. See
[Webhooks and events](/core-components/webhooks-and-events) for delivery, retries, and
signature verification.

| Event                                        | Fired when the intent enters |
| -------------------------------------------- | ---------------------------- |
| `enrolment_intent.processing`                | `processing`                 |
| `enrolment_intent.scheduled`                 | `scheduled`                  |
| `enrolment_intent.action_required`           | `action_required`            |
| `enrolment_intent.coverage_options_required` | `coverage_options_required`  |
| `enrolment_intent.pending_confirmation`      | `pending_confirmation`       |
| `enrolment_intent.enrolling`                 | `enrolling`                  |
| `enrolment_intent.enrolled`                  | `enrolled`                   |
| `enrolment_intent.ineligible`                | `ineligible`                 |
| `enrolment_intent.not_undertaken`            | `not_undertaken`             |

Each payload identifies the intent so you can fetch its current state:

```json
{
  "type": "enrolment_intent.pending_confirmation",
  "data": {
    "object": "enrolment_intent.event",
    "resource_type": "enrolment_intent",
    "enrolment_intent_id": "ei_8f3a1c20d4e9457bb1f0a9c2e7d61234",
    "employee_id": "ee_2b9c4d6e8f0a1b3c5d7e9f0a1b2c3d4e",
    "group_id": "gr_a1b2c3d4e5f60718293a4b5c6d7e8f90"
  }
}
```

On receiving an event, call retrieve to read the authoritative state and any
status-specific detail (`action_required`, `pending_confirmation`,
`ineligibility_reason`, or coverage options under `policy_enrolments`).

### Retrieve or list (for reconciliation)

Use these to read the latest state on demand, after a webhook, or to catch up if one was
missed. They aren't a substitute for webhooks; don't call them on a tight loop waiting for
a transition.

**Endpoint:** [`GET /enrolment_intents/{enrolment_intent_id}`](/api-reference/enrolment-intents/retrieve-enrolment-intent)

**`cURL`**

```bash cURL
curl -X GET "https://api.kota.io/enrolment_intents/ei_8f3a1c20d4e9457bb1f0a9c2e7d61234" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

To reconcile in bulk, list and filter by `employee_id`, `group_id`, or one or more
`status` values (comma-separated):

**Endpoint:** [`GET /enrolment_intents`](/api-reference/enrolment-intents/list-enrolment-intents)

**`cURL`**

```bash cURL
curl -X GET "https://api.kota.io/enrolment_intents?group_id=gr_a1b2c3d4e5f60718293a4b5c6d7e8f90&status=pending_confirmation,action_required,coverage_options_required" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

---

## Step 3: Handle the states that need input

Depending on the plan and provider, the intent may pause and wait for you.

### `action_required`

When `status` is `action_required`, the intent needs more information before it can
proceed (for example dependant or beneficiary details). Inspect `action_required` for
what's needed and the deadline:

```json
{
  "action_required": {
    "code": "provide_dependant_information",
    "reason": "Dependant information is required",
    "reason_description": "Provide details for the employee's dependants to continue.",
    "due_by": "2026-07-06T00:00:00Z"
  }
}
```

Collect and submit the required information. Once it's complete, the intent advances
automatically. Watch for the next status webhook.

The action payload tells you how to resolve the requirement. It always includes a `code`
and the information needed to complete the next step, such as adaptive requirement IDs
or details about dependant or other enrolment information that must be collected.
Depending on the action, your integration may resolve it through
[adaptive requirements](/api/adaptive-requirements) or by submitting the requested
related data.

If `due_by` is present, treat it as the provider's deadline for receiving the requested
information. Missing the deadline may negatively affect the policy start date, for
example by pushing the policy start further into the future than necessary.

### `coverage_options_required`

When `status` is `coverage_options_required`, the employee must choose policy-scoped
coverage options before enrolment can continue. Retrieve the intent and read the available
options from each health insurance policy enrolment:

`policy_enrolments[].health_insurance.coverage_options`

Each coverage configuration includes an `id` (prefixed `pc_`), display fields, and the
selectable options (`pco_` IDs). Present those options to the employee, then submit the
selections.

**Endpoint:** [`POST /enrolment_intents/{enrolment_intent_id}/coverage-selections`](/api-reference/enrolment-intents/submit-enrolment-intent-coverage-selections)

**`cURL`**

```bash cURL
curl -X POST "https://api.kota.io/enrolment_intents/ei_8f3a1c20d4e9457bb1f0a9c2e7d61234/coverage-selections" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "coverage_selections": [
      {
        "configuration_id": "pc_3b1333d87d9d4fd6ad83ba7f6b0e951a",
        "options": [
          {
            "option_id": "pco_3b1333d87d9d4fd6ad83ba7f6b0e951a"
          }
        ]
      }
    ]
  }'
```

A successful submit returns `204 No Content`. The intent then advances (commonly to
`pending_confirmation` or further processing). If the employee chooses not to continue,
reject the intent instead: this status is one of the states that
[`POST /enrolment_intents/{enrolment_intent_id}/reject`](/api-reference/enrolment-intents/reject-enrolment-intent)
accepts, which moves it to `not_undertaken`.

You can also pass valid `coverage_selections` when
[creating the enrolment intent](/api-reference/enrolment-intents/create-enrolment-intent).
If those selections satisfy the policy-scoped configurations, Kota skips the
`coverage_options_required` step.

### `ineligible`

When `status` is `ineligible`, the employee can't be enrolled. `ineligibility_reason`
explains why:

```json
{
  "ineligibility_reason": {
    "code": "employee_above_maximum_age",
    "reason": "The employee is above the maximum age accepted by this plan."
  }
}
```

This is terminal for the intent. Resolve the underlying cause and create a new intent.
Prefer displaying the `reason` string to end users and treat `code` as a hint, with a
fallback for unrecognised values.

Ineligibility reasons are generally non-recoverable for the current intent. The main
exception is when the employee is not active yet, which may be recoverable in some cases.
When in doubt, show the human-readable reason, resolve the underlying issue if possible,
and create a new intent.

### Updating before enrolment

To change the policy configuration before enrolment completes, update the intent. This
resets it to `processing` and re-evaluates it. It's **not** available once the intent is
`enrolling`, `enrolled`, or `not_undertaken`.

**Endpoint:** [`PUT /enrolment_intents/{enrolment_intent_id}`](/api-reference/enrolment-intents/update-enrolment-intent-configuration)

**`cURL`**

```bash cURL
curl -X PUT "https://api.kota.io/enrolment_intents/ei_8f3a1c20d4e9457bb1f0a9c2e7d61234" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "desired_policy_start_date": "2026-08-01" }'
```

---

## Step 4: Confirm enrolment

When `status` is `pending_confirmation`, the employee must explicitly agree before
enrolment proceeds. This happens for opt-in benefits, or whenever you created the intent
with `force_confirmation: true`.

Show the employee the intent's `disclosures` and the `pending_confirmation.reason` first,
then confirm. See the [Insurance Disclosures guide](/api/insurance-disclosures)
for how to retrieve and display disclosures.

**Endpoint:** [`POST /enrolment_intents/{enrolment_intent_id}/confirm`](/api-reference/enrolment-intents/confirm-enrolment-intent)

**`cURL`**

```bash cURL
curl -X POST "https://api.kota.io/enrolment_intents/ei_8f3a1c20d4e9457bb1f0a9c2e7d61234/confirm" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Confirming returns `204 No Content` and moves the intent to `enrolling`.

> **Warning**
>
> Confirmation is a commitment to enrol the employee into the policy. Make sure the
> employee has reviewed the disclosures and explicitly agreed before calling this endpoint.

---

## Step 5: Enrolment completes

Kota enrols the employee with the provider (`enrolling`). When the policy is issued the
intent becomes `enrolled`, you receive `enrolment_intent.enrolled`, and
`policy_enrolments[].id` (prefixed `p_`) is populated. No further action is needed.

`policy_enrolments` is an array because one enrolment intent can create more than one
policy. This can happen when the group is backed by a bundle of benefits instead of a
single benefit.

```json
{
  "status": "enrolled",
  "policy_enrolments": [
    {
      "type": "health_insurance",
      "estimated_effective_from": "2026-07-01",
      "id": "p_4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f90"
    }
  ]
}
```

---

## Webhooks summary

Handle these events to drive your UI:

| Event                                        | Description                               | Suggested UI                   |
| -------------------------------------------- | ----------------------------------------- | ------------------------------ |
| `enrolment_intent.processing`                | Intent is being evaluated                 | Loading state                  |
| `enrolment_intent.scheduled`                 | Waiting for the employee to become active | Informational, "starts on …"   |
| `enrolment_intent.action_required`           | More information needed                   | Requirement form with deadline |
| `enrolment_intent.coverage_options_required` | Coverage options must be selected         | Coverage option selection UI   |
| `enrolment_intent.pending_confirmation`      | Awaiting employee confirmation            | Disclosures + confirm/reject   |
| `enrolment_intent.enrolling`                 | Enrolling with the provider               | Holding screen                 |
| `enrolment_intent.enrolled`                  | Policy issued                             | Success / policy details       |
| `enrolment_intent.ineligible`                | Employee can't be enrolled                | Show `reason`                  |
| `enrolment_intent.not_undertaken`            | Intent was rejected                       | Allow restarting               |

---

## Best practices

* **React to webhooks, don't poll or time it.** Steps complete in variable time (seconds
  to days, depending on the insurer), so act on events as they arrive rather than polling
  on an interval or assuming a duration. Use retrieve/list only for reconciliation.
* **Drive off `status`, not assumptions.** Re-fetch the intent when you receive a webhook
  and branch on the current `status`; states can change between your reads.
* **Idempotency.** Always send an `Idempotency-Key` on creation.
* **Display reasons, not codes.** Use the `reason` / `reason_description` fields for
  end-user messaging and treat enum `code`s as programmatic hints with a fallback.
* **Respect deadlines.** Surface `due_by` on `action_required` and `pending_confirmation`
  so employees act in time.

---

## Next steps

* [Assign Employee to a Group](/api/employee-flow/assign-employee-to-group): add employees to benefit groups before enrolment
* [Adaptive Requirements Guide](/api/adaptive-requirements): retrieve schemas and submit answers for `action_required`
* [API Reference: Enrolment Intents](/api-reference/enrolment-intents/create-enrolment-intent): full endpoint and schema reference
* [Webhooks and events](/core-components/webhooks-and-events): event handling
* [API Errors](/api-reference/errors): error response format
* [Employer Health Insurance Setup](/api/employer-health-insurance): how groups and group policies are created