Employee Enrolment

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.

Before starting, ensure you’ve completed:

This guide assumes the employer already has an active group policy and the employee has been assigned to the group. See the Employer Health Insurance Setup 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 or by polling the intent.

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

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.

StatusMeaningWhat you do
processingKota is evaluating eligibility and preparing the enrolment.Wait. Transient.
scheduledThe employee isn’t active yet (e.g. future start date). Picked up automatically once they become active.Waiting for employee to be Active.
ineligibleThe employee can’t be enrolled. See ineligibility_reason.Resolve the cause, then create a new intent.
action_requiredMore information is needed before enrolment can proceed. See action_required.Collect and submit the required information.
pending_confirmationEnrolment is ready but needs explicit confirmation (opt-in benefit, or force_confirmation).Confirm (or reject) the intent.
enrollingConfirmed. Kota is enrolling the employee with the provider.Wait. Transient.
enrolledThe policy has been issued. policy_enrolments[].id is populated.Done.
not_undertakenThe intent was rejected and will not proceed.Terminal. Create a new intent to retry.

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

Request body:

FieldTypeRequiredDescription
employee_idstringYesThe employee to enrol (prefixed ee_).
group_idstringYesThe group to enrol them into (prefixed gr_).
force_confirmationbooleanNoForce the intent through pending_confirmation even when the provider would not otherwise require explicit confirmation. Defaults to false.
policy_configurationobjectNoOverrides 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.

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.

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
$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:

1{
2 "object": "enrolment_intent",
3 "id": "ei_8f3a1c20d4e9457bb1f0a9c2e7d61234",
4 "employee_id": "ee_2b9c4d6e8f0a1b3c5d7e9f0a1b2c3d4e",
5 "group_id": "gr_a1b2c3d4e5f60718293a4b5c6d7e8f90",
6 "status": "processing",
7 "force_confirmation": false,
8 "policy_enrolments": [
9 {
10 "type": "health_insurance",
11 "estimated_effective_from": "2026-07-01",
12 "provider": { "id": "pr_allianz", "name": "Allianz" },
13 "plan": { "id": "pl_xyz789", "name": "Health Plan – Level 2", "currency": "EUR" },
14 "id": null
15 }
16 ],
17 "ineligibility_reason": null,
18 "action_required": null,
19 "pending_confirmation": null,
20 "disclosures": []
21}

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.

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

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.

Subscribe to the enrolment-intent events. Kota emits one per status transition. See Webhooks and events for delivery, retries, and signature verification.

EventFired when the intent enters
enrolment_intent.processingprocessing
enrolment_intent.scheduledscheduled
enrolment_intent.action_requiredaction_required
enrolment_intent.pending_confirmationpending_confirmation
enrolment_intent.enrollingenrolling
enrolment_intent.enrolledenrolled
enrolment_intent.ineligibleineligible
enrolment_intent.not_undertakennot_undertaken

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

1{
2 "type": "enrolment_intent.pending_confirmation",
3 "data": {
4 "object": "enrolment_intent.event",
5 "resource_type": "enrolment_intent",
6 "enrolment_intent_id": "ei_8f3a1c20d4e9457bb1f0a9c2e7d61234",
7 "employee_id": "ee_2b9c4d6e8f0a1b3c5d7e9f0a1b2c3d4e",
8 "group_id": "gr_a1b2c3d4e5f60718293a4b5c6d7e8f90"
9 }
10}

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

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}

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

cURL
$curl -X GET "https://api.kota.io/enrolment_intents?group_id=gr_a1b2c3d4e5f60718293a4b5c6d7e8f90&status=pending_confirmation,action_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:

1{
2 "action_required": {
3 "code": "provide_dependant_information",
4 "reason": "Dependant information is required",
5 "reason_description": "Provide details for the employee's dependants to continue.",
6 "due_by": "2026-07-06T00:00:00Z"
7 }
8}

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, coverage options, or details about dependant or other enrolment information that must be collected. Depending on the action, your integration may resolve it through 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.

ineligible

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

1{
2 "ineligibility_reason": {
3 "code": "employee_above_maximum_age",
4 "reason": "The employee is above the maximum age accepted by this plan."
5 }
6}

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}

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 Disclosures guide for how to retrieve and display disclosures.

Endpoint: POST /enrolment_intents/{enrolment_intent_id}/confirm

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.

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.

1{
2 "status": "enrolled",
3 "policy_enrolments": [
4 {
5 "type": "health_insurance",
6 "estimated_effective_from": "2026-07-01",
7 "id": "p_4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f90"
8 }
9 ]
10}

Webhooks summary

Handle these events to drive your UI:

EventDescriptionSuggested UI
enrolment_intent.processingIntent is being evaluatedLoading state
enrolment_intent.scheduledWaiting for the employee to become activeInformational, “starts on …”
enrolment_intent.action_requiredMore information neededRequirement form with deadline
enrolment_intent.pending_confirmationAwaiting employee confirmationDisclosures + confirm/reject
enrolment_intent.enrollingEnrolling with the providerHolding screen
enrolment_intent.enrolledPolicy issuedSuccess / policy details
enrolment_intent.ineligibleEmployee can’t be enrolledShow reason
enrolment_intent.not_undertakenIntent was rejectedAllow 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 codes as programmatic hints with a fallback.
  • Respect deadlines. Surface due_by on action_required and pending_confirmation so employees act in time.

Next steps