Employer Health Insurance Setup

This guide walks you through building the employer-facing experience for setting up group health insurance. You’ll learn how to display available plans, request quotes, handle requirements, and activate group policies - all through Kota’s API.

Before starting, ensure you’ve completed:


Overview

The employer health insurance setup follows Kota’s intent-based pattern. Two primary intents drive the flow:

  1. Group Quote Intent - Initiates the quoting process and returns pricing from insurers
  2. Group Setup Intent - Accepts a quote and establishes the group policy

Each intent may require additional data from employers or employees before completion. The API returns dynamic requirements that your platform must collect and submit.

High-Level Flow

┌─────────────────────────────────────────────────────────────────────────┐
│ DISCOVERY PART │
├─────────────────────────────────────────────────────────────────────────┤
│ List Providers → List Plans → Display to Employer │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ GROUP CREATION │
├─────────────────────────────────────────────────────────────────────────┤
│ Employer selects plan → Create Group for the employer │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ GROUP QUOTE INTENT │
├─────────────────────────────────────────────────────────────────────────┤
│ Create Group Quote Intent → Handle Requirements → Quote Available │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ GROUP SETUP INTENT │
├─────────────────────────────────────────────────────────────────────────┤
│ Create Group Setup Intent → Handle Requirements → Policy Active │
└─────────────────────────────────────────────────────────────────────────┘

Part 1: Discovery

Before employers can request quotes, they need to see what’s available. This part involves fetching providers and plans to display in your UI.

Step 1: List Providers

Retrieve the list of insurance providers available on your platform. Each provider includes the countries they support.

Endpoint: GET /providers

cURL
$curl -X GET "https://api.kota.io/providers" \
> -H "Authorization: Bearer YOUR_API_KEY"

Response:

1{
2 "object": "list",
3 "data": [
4 {
5 "object": "provider",
6 "id": "pr_hallesche",
7 "name": "Hallesche",
8 "description": "German private health insurance provider",
9 "website_url": "https://www.hallesche.de",
10 "logo_url": "https://...",
11 "supported_countries": ["DE"]
12 },
13 {
14 "object": "provider",
15 "id": "pr_allianz",
16 "name": "Allianz",
17 "description": "International insurance provider",
18 "website_url": "https://www.allianz.com",
19 "logo_url": "https://...",
20 "supported_countries": ["DE", "ES", "NL", "IE"]
21 }
22 ],
23 "has_more": false
24}

Implementation notes:

  • Filter providers by country based on your employer’s location
  • Store provider IDs for use when fetching plans
  • Display provider logos and descriptions to help employers make informed choices

Step 2: List Plans

Retrieve available health insurance plans. Filter by provider and/or country to show relevant options.

Endpoint: GET /plans

Query parameters:

ParameterTypeDescription
typestringFilter by benefit type. Use health_insurance
provider_idstringFilter by provider ID. Use the id field from the providers response (e.g., pr_hallesche). Comma-separated for multiple.
countrystringFilter by country code (e.g., DE, ES, IE)
cURL
$curl -X GET "https://api.kota.io/plans?type=health_insurance&country=DE&provider_id=pr_hallesche" \
> -H "Authorization: Bearer YOUR_API_KEY"

Response:

1{
2 "object": "list",
3 "data": [
4 {
5 "object": "plan",
6 "id": "pl_abc123",
7 "type": "health_insurance",
8 "name": "Hallesche Premium Health",
9 "description": "Comprehensive private health insurance",
10 "country": "DE",
11 "provider": {
12 "id": "pr_hallesche",
13 "name": "Hallesche",
14 "description": "German private health insurance provider",
15 "logo_url": "https://..."
16 },
17 "documents": [
18 {
19 "type": "ipid",
20 "title": "Insurance Product Information Document",
21 "link": "https://..."
22 }
23 ],
24 "employer_eligibility_criteria": [
25 {
26 "type": "employees_count",
27 "description": "Minimum 3 employees required"
28 }
29 ],
30 "employee_eligibility_criteria": [
31 {
32 "type": "age_range",
33 "description": "Employee must be under 65 years old"
34 }
35 ],
36 "health_insurance": {
37 "insurance_geographical_need": "international",
38 "supported_cost_sharing_options": ["percentage", "fixed_amount", "member_count"],
39 "supported_member_types": ["adult", "young_adult", "child"],
40 "pricing": {
41 "type": "per_member",
42 "per_member": {
43 "currency": "EUR",
44 "member_type_pricing": [
45 { "code": "adult", "display_name": "Adult", "monthly_premium": 150.00 },
46 { "code": "young_adult", "display_name": "Young Adult", "monthly_premium": 100.00 },
47 { "code": "child", "display_name": "Child", "monthly_premium": 75.00 }
48 ]
49 }
50 }
51 }
52 }
53 ],
54 "has_more": false
55}

Implementation notes:

  • Display plan details, coverage information, and eligibility criteria
  • Show plan documents (IPIDs, terms and conditions) for employer review
  • Highlight any eligibility restrictions that might affect the employer’s decision like minimum employees, age, etc.
  • Use the health_insurance.pricing block to display plan costs per member type
  • Check health_insurance.supported_cost_sharing_options to determine which cost sharing options to offer in your UI

Step 3: Create a Group

Once the employer selects a plan, create a Group to represent their health insurance setup. The Group ties together the employer, the selected plan, and eventually the group policy.

Endpoint: POST /groups

Request body:

FieldTypeRequiredDescription
employer_idstringYesThe employer creating the group (prefixed with er_)
namestringYesHuman-readable name for the group
descriptionstringNoOptional description of the group’s purpose
cURL
$curl -X POST "https://api.kota.io/groups" \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "employer_id": "er_abc123",
> "name": "Health Insurance - Hallesche Premium",
> "description": "Group health insurance for all employees"
> }'

Response:

1{
2 "object": "group",
3 "id": "gr_abc123",
4 "name": "Health Insurance - Hallesche Premium",
5 "description": "Group health insurance for all employees",
6 "status": "pending",
7 "group_type": "single",
8 "employer_id": "er_abc123",
9 "group_policy_ids": []
10}

The Group starts in pending status. It will transition to ready once the group policy is created and active.

Implementation notes:

  • Store the group_id for use in the Group Quote Intent
  • Use a descriptive name that helps employers identify the group (e.g., include the plan or provider name)
  • The group_type will be single for a single health insurance policy

Part 2: Group Quote Intent

When an employer selects a plan, create a Group Quote Intent to initiate the quoting process. Different providers have different response times - some provide instant quotes, others may take up to a few days.

Group Quote Intent Lifecycle

┌──────────────┐
│ Created │
└──────┬───────┘
┌──────────────┐
│ Processing │
└──────┬───────┘
┌─────────────────────────┐
│ Missing requirements? │
└─────────────┬───────────┘
┌──────────────┴──────────────┐
↓ yes ↓ no
┌──────────────────┐ │
│ Action Required │ │
└────────┬─────────┘ │
│ │
│ (fulfill requirements) │
│ │
└──────────────┬───────────────┘
┌─────────────────┐
│ Awaiting Quote │
└────────┬────────┘
┌─────────────────┐
│ Quote Available │
└────────┬────────┘
┌────────────────────┼────────────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Accepted │ │ Rejected │ │ Expired │
│ (→ Setup) │ │ (by user │ │ │
│ │ │ or insurer)│ │ │
└─────────────┘ └─────────────┘ └─────────────┘

Step 4: Create Group Quote Intent

Create a Group Quote Intent when the employer is ready to request pricing for a specific plan.

Endpoint: POST /group_quote_intents

Request body:

FieldTypeRequiredDescription
group_idstringYesThe group requesting the quote (prefixed with gr_)
plan_idstringYesThe selected plan (prefixed with pl_)
cost_sharingobjectYesHow premiums are split between employer and employee

The cost_sharing object determines how costs are allocated. The available types depend on the plan - check the health_insurance.supported_cost_sharing_options field on the plan response to see which options are available.

Cost sharing types:

TypeDescriptionExample
percentageEmployer pays a percentage of the premium{ "type": "percentage", "percentage": { "employer_percentage": 100 } }
member_countCoverage based on number of adults/children{ "type": "member_count", "member_count": { "adults": 1, "children": 0 } }
member_selectionExplicit partner/children coverage selection{ "type": "member_selection", "member_selection": { "partner": true, "children": false } }
policyholder_onlyCoverage for employee only{ "type": "policyholder_only" }
family_typeFamily structure based coverage{ "type": "family_type", "family_type": { "type": "single" } }
cURL
$curl -X POST "https://api.kota.io/group_quote_intents" \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "group_id": "gr_abc123",
> "plan_id": "pl_xyz789",
> "cost_sharing": {
> "type": "percentage",
> "percentage": {
> "employer_percentage": 100
> }
> }
> }'

Response:

1{
2 "object": "group_quote_intent",
3 "id": "gqi_def456",
4 "status": "processing",
5 "group_id": "gr_abc123",
6 "plan_id": "pl_xyz789",
7 "cost_sharing": {
8 "type": "percentage",
9 "percentage": {
10 "employer_percentage": 100
11 }
12 }
13}

After creation, the Group Quote Intent enters processing state. Kota evaluates whether additional information is needed.

Step 5: Handle Requirements (if any)

If the Group Quote Intent transitions to action_required, requirements must be fulfilled before a quote can be generated.

Webhook: group_quote_intent.action_required

1{
2 "type": "group_quote_intent.action_required",
3 "data": {
4 "group_quote_intent_id": "gqi_def456",
5 "group_id": "gr_abc123",
6 "plan_id": "pl_xyz789",
7 "status": "action_required"
8 }
9}

Fetching requirement details:

Use the Group Quote Intent Requirements API to get all requirements. Each requirement includes a JSON Schema that describes exactly what data is needed.

Endpoint: GET /group_quote_intents/{groupQuoteIntentId}/requirements

cURL
$curl -X GET "https://api.kota.io/group_quote_intents/gqi_def456/requirements" \
> -H "Authorization: Bearer YOUR_API_KEY"

Response:

1{
2 "object": "list",
3 "data": [
4 {
5 "object": "group_quote_intent_requirement",
6 "id": "550e8400-e29b-41d4-a716-446655440000",
7 "object_type": "employer",
8 "object_id": "er_abc123",
9 "is_fulfilled": false,
10 "requirement_type": "company_registration"
11 },
12 {
13 "object": "group_quote_intent_requirement",
14 "id": "550e8400-e29b-41d4-a716-446655440001",
15 "object_type": "employee",
16 "object_id": "ee_xyz789",
17 "is_fulfilled": false,
18 "requirement_type": "date_of_birth"
19 }
20 ],
21 "has_more": false
22}

Requirements Examples

1{
2 "id": "dependant_in_canadalife",
3 "version": 1,
4 "object_type": "associated_person",
5 "benefit_type": "health",
6 "context": "dependant_management_intent",
7 "title": { "default": "Add dependant" },
8 "datasets": [
9 {
10 "id": "sex_at_birth",
11 "items": [
12 { "value": "male", "label": { "default": "Male" } },
13 { "value": "female", "label": { "default": "Female" } }
14 ]
15 },
16 {
17 "id": "canadalife_member_type",
18 "items": [
19 { "value": "spouse", "label": { "default": "Spouse/Partner" } },
20 { "value": "child", "label": { "default": "Child" } }
21 ]
22 },
23 {
24 "id": "yes_no",
25 "items": [
26 { "value": true, "label": { "default": "Yes" } },
27 { "value": false, "label": { "default": "No" } }
28 ]
29 }
30 ],
31 "fields": [
32 {
33 "id": "dependant_id",
34 "type": "hidden",
35 "label": { "default": "Dependant ID" }
36 },
37 {
38 "id": "insurer_id",
39 "type": "hidden",
40 "label": { "default": "Insurer ID" },
41 "defaultValue": "in_canadalife",
42 "validation": { "required": true }
43 },
44 {
45 "id": "first_name",
46 "type": "text",
47 "label": { "default": "First name" },
48 "placeholder": "Jane",
49 "validation": { "required": true, "minLength": 1 }
50 },
51 {
52 "id": "last_name",
53 "type": "text",
54 "label": { "default": "Last name" },
55 "placeholder": "Doe",
56 "validation": { "required": true, "minLength": 1 }
57 },
58 {
59 "id": "date_of_birth",
60 "type": "date",
61 "label": { "default": "Date of birth" },
62 "validation": {
63 "required": true,
64 "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
65 "validators": [
66 { "name": "dob_not_in_future" },
67 {
68 "name": "age_range",
69 "params": {
70 "min": 18,
71 "max": 100,
72 "inclusiveMax": true,
73 "when": { "==": [{ "var": "answers.member_type" }, "spouse"] }
74 }
75 },
76 {
77 "name": "age_range",
78 "params": {
79 "min": 0,
80 "max": 21,
81 "inclusiveMax": false,
82 "when": {
83 "and": [
84 { "==": [{ "var": "answers.member_type" }, "child"] },
85 { "==": [{ "var": "answers.is_a_student" }, false] }
86 ]
87 }
88 }
89 },
90 {
91 "name": "age_range",
92 "params": {
93 "min": 0,
94 "max": 25,
95 "inclusiveMax": false,
96 "when": {
97 "and": [
98 { "==": [{ "var": "answers.member_type" }, "child"] },
99 { "==": [{ "var": "answers.is_a_student" }, true] }
100 ]
101 }
102 }
103 }
104 ]
105 }
106 },
107 {
108 "id": "member_type",
109 "type": "radio",
110 "label": { "default": "Dependant type" },
111 "optionsSource": { "dataset": "canadalife_member_type" },
112 "validation": { "required": true }
113 },
114 {
115 "id": "sex_at_birth",
116 "type": "radio",
117 "label": { "default": "Sex assigned at birth" },
118 "optionsSource": { "dataset": "sex_at_birth" },
119 "validation": { "required": true }
120 },
121 {
122 "id": "common_law_spouse",
123 "type": "toggle",
124 "label": { "default": "Common Law Relationship?" },
125 "description": "You live together in a long-term relationship.",
126 "visibleWhen": { "==": [{ "var": "answers.member_type" }, "spouse"] },
127 "excludeWhen": { "!": { "==": [{ "var": "answers.member_type" }, "spouse"] } },
128 "validation": {
129 "requireWhen": { "==": [{ "var": "answers.member_type" }, "spouse"] }
130 }
131 },
132 {
133 "id": "is_already_covered",
134 "type": "toggle",
135 "label": { "default": "Is already covered by another insurer?" },
136 "description": "Does this spouse/partner already have health coverage through another insurer?",
137 "visibleWhen": { "==": [{ "var": "answers.member_type" }, "spouse"] },
138 "excludeWhen": { "!": { "==": [{ "var": "answers.member_type" }, "spouse"] } },
139 "validation": {
140 "requireWhen": { "==": [{ "var": "answers.member_type" }, "spouse"] }
141 }
142 },
143 {
144 "id": "cohabitation_start_date",
145 "type": "date",
146 "label": { "default": "Cohabitation start date" },
147 "visibleWhen": {
148 "and": [
149 { "==": [{ "var": "answers.member_type" }, "spouse"] },
150 { "==": [{ "var": "answers.common_law_spouse" }, true] }
151 ]
152 },
153 "excludeWhen": {
154 "!": {
155 "and": [
156 { "==": [{ "var": "answers.member_type" }, "spouse"] },
157 { "==": [{ "var": "answers.common_law_spouse" }, true] }
158 ]
159 }
160 },
161 "validation": {
162 "requireWhen": {
163 "and": [
164 { "==": [{ "var": "answers.member_type" }, "spouse"] },
165 { "==": [{ "var": "answers.common_law_spouse" }, true] }
166 ]
167 },
168 "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
169 }
170 },
171 {
172 "id": "is_a_student",
173 "type": "toggle",
174 "label": { "default": "Full time Student" },
175 "visibleWhen": { "==": [{ "var": "answers.member_type" }, "child"] },
176 "excludeWhen": { "!": { "==": [{ "var": "answers.member_type" }, "child"] } },
177 "validation": {
178 "requireWhen": { "==": [{ "var": "answers.member_type" }, "child"] }
179 }
180 },
181 {
182 "id": "has_disability",
183 "type": "toggle",
184 "label": { "default": "Has Disability" },
185 "description": "Children who are incapable of supporting themselves because of physical or mental disorder are covered without age limit if the disorder begins before they turn 21, or while they are students under 25, and the disorder has been continuous since that time.",
186 "visibleWhen": { "==": [{ "var": "answers.member_type" }, "child"] },
187 "excludeWhen": { "!": { "==": [{ "var": "answers.member_type" }, "child"] } },
188 "validation": {
189 "requireWhen": { "==": [{ "var": "answers.member_type" }, "child"] }
190 }
191 },
192 {
193 "id": "school_start_date",
194 "type": "date",
195 "label": { "default": "School start date" },
196 "visibleWhen": {
197 "and": [
198 { "==": [{ "var": "answers.member_type" }, "child"] },
199 { "==": [{ "var": "answers.is_a_student" }, true] }
200 ]
201 },
202 "excludeWhen": {
203 "!": {
204 "and": [
205 { "==": [{ "var": "answers.member_type" }, "child"] },
206 { "==": [{ "var": "answers.is_a_student" }, true] }
207 ]
208 }
209 },
210 "validation": {
211 "requireWhen": {
212 "and": [
213 { "==": [{ "var": "answers.member_type" }, "child"] },
214 { "==": [{ "var": "answers.is_a_student" }, true] }
215 ]
216 },
217 "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
218 }
219 },
220 {
221 "id": "school_end_date",
222 "type": "date",
223 "label": { "default": "School end date" },
224 "visibleWhen": {
225 "and": [
226 { "==": [{ "var": "answers.member_type" }, "child"] },
227 { "==": [{ "var": "answers.is_a_student" }, true] }
228 ]
229 },
230 "excludeWhen": {
231 "!": {
232 "and": [
233 { "==": [{ "var": "answers.member_type" }, "child"] },
234 { "==": [{ "var": "answers.is_a_student" }, true] }
235 ]
236 }
237 },
238 "validation": {
239 "requireWhen": {
240 "and": [
241 { "==": [{ "var": "answers.member_type" }, "child"] },
242 { "==": [{ "var": "answers.is_a_student" }, true] }
243 ]
244 },
245 "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
246 "validators": [{ "name": "date_after", "params": { "afterField": "school_start_date" } }]
247 }
248 }
249 ],
250 "flow": {
251 "mode": "auto",
252 "steps": [
253 {
254 "id": "general_info",
255 "title": { "default": "General information" },
256 "fields": [
257 "dependant_id",
258 "insurer_id",
259 "first_name",
260 "last_name",
261 "date_of_birth",
262 "member_type",
263 "sex_at_birth"
264 ]
265 },
266 {
267 "id": "conditional_info",
268 "title": { "default": "Additional information" },
269 "fields": [
270 "common_law_spouse",
271 "is_already_covered",
272 "cohabitation_start_date",
273 "is_a_student",
274 "has_disability",
275 "school_start_date",
276 "school_end_date"
277 ]
278 }
279 ],
280 "navigation": { "start": "general_info" }
281 }
282}

Implementation notes:

  • Requirements can be for the employer (object_type: "employer") or employees (object_type: "employee")
  • The requirement_type field indicates what data is needed (e.g., company_registration, date_of_birth)
  • Use is_fulfilled to track which requirements have been completed
  • The object_id tells you which employer or employee the requirement applies to

Submitting requirement data:

Endpoint: PUT /requirements/{requirement_id}

cURL
$curl -X PUT "https://api.kota.io/requirements/req_001" \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "data": {
> "registration_number": "HRB 12345",
> "registered_address": {
> "line1": "Hauptstraße 1",
> "city": "Berlin",
> "postal_code": "10115",
> "country": "DE"
> }
> }
> }'

Submit requirement data using the requirement’s specific endpoint. Once all requirements are fulfilled, the Group Quote Intent automatically transitions to awaiting_quote.

Step 6: Await Quote

After requirements are satisfied (or if none were needed), the Group Quote Intent enters awaiting_quote state. This means Kota is waiting for the insurer to respond with pricing.

Webhook: group_quote_intent.awaiting_quote

1{
2 "type": "group_quote_intent.awaiting_quote",
3 "data": {
4 "group_quote_intent_id": "gqi_def456",
5 "group_id": "gr_abc123",
6 "plan_id": "pl_xyz789",
7 "status": "awaiting_quote"
8 }
9}

Response times vary by provider. Some providers return quotes almost instantly. Others may take 1-3 business days. Display an appropriate holding screen to set employer expectations.

Step 7: Quote Available

When the insurer responds, the Group Quote Intent transitions to quote_available. You can retrieve the full quote details using the quote endpoint.

Webhook: group_quote_intent.quote_available

1{
2 "type": "group_quote_intent.quote_available",
3 "data": {
4 "group_quote_intent_id": "gqi_def456",
5 "group_id": "gr_abc123",
6 "plan_id": "pl_xyz789",
7 "status": "quote_available"
8 }
9}

Fetching quote details:

Endpoint: GET /group_quote_intents/{groupQuoteIntentId}/quote

cURL
$curl -X GET "https://api.kota.io/group_quote_intents/gqi_def456/quote" \
> -H "Authorization: Bearer YOUR_API_KEY"

Response:

1{
2 "object": "group_quote",
3 "employee_count": 5,
4 "total_monthly_premium": 1250.00,
5 "currency": "EUR",
6 "cost_sharing": {
7 "type": "percentage",
8 "percentage": {
9 "employer_percentage": 100
10 }
11 },
12 "pdf_url": "https://...",
13 "pdf_expires_at": "2024-01-16T10:30:00Z",
14 "generated_at": "2024-01-15T10:30:00Z",
15 "expires_at": "2024-02-15T23:59:59Z"
16}

Display the quote to the employer with options to:

  • Accept - Proceed to setup (creates a Group Setup Intent)
  • Reject - Decline the quote
  • Let expire - Take no action (quote expires at expires_at)

Alternative Outcomes

Quote rejected by insurer:

In some cases, the insurer may decline to provide a quote.

Webhook: group_quote_intent.rejected_by_insurer

1{
2 "type": "group_quote_intent.rejected_by_insurer",
3 "data": {
4 "group_quote_intent_id": "gqi_def456",
5 "group_id": "gr_abc123",
6 "plan_id": "pl_xyz789",
7 "status": "rejected_by_insurer"
8 }
9}

Quote expired:

If the employer doesn’t act before expires_at, the quote expires.

Webhook: group_quote_intent.quote_expired

Rejecting a quote:

If the employer decides not to proceed:

Endpoint: POST /group_quote_intents/{groupQuoteIntentId}/reject

cURL
$curl -X POST "https://api.kota.io/group_quote_intents/gqi_def456/reject" \
> -H "Authorization: Bearer YOUR_API_KEY"

Part 3: Group Setup Intent

When the employer accepts a quote, create a Group Setup Intent to establish the group policy with the insurer.

Group Setup Intent Lifecycle

┌──────────────┐
│ Created │
└──────┬───────┘
┌──────────────┐
│ Processing │
└──────┬───────┘
┌─────────────────────────┐
│ Missing requirements? │
└─────────────┬───────────┘
┌──────────────┴──────────────┐
↓ yes ↓ no
┌──────────────────┐ │
│ Action Required │ │
└────────┬─────────┘ │
│ │
│ (fulfill requirements) │
│ │
└──────────────┬───────────────┘
┌─────────────────────────┐
│ Pending Insurer │
│ Approval │
└───────────┬─────────────┘
┌─────────────┴─────────────┐
↓ ↓
┌──────────────────┐ ┌─────────────────────┐
│ Group Policy │ │ Rejected by │
│ Scheduled/Active │ │ Insurer (rare) │
└──────────────────┘ └─────────────────────┘

After a successful setup, you’ll receive either group_policy.scheduled (if the coverage start date is in the future) or group_policy.active (if coverage starts immediately). The setup intent can also be rejected by the insurer during final review.

Step 8: Create Group Setup Intent

Accept the quote and initiate policy setup.

Endpoint: POST /group_setup_intents

Request body:

FieldTypeRequiredDescription
group_quote_intent_idstringYesThe Group Quote Intent with the accepted quote
cURL
$curl -X POST "https://api.kota.io/group_setup_intents" \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "group_quote_intent_id": "gqi_def456"
> }'

Response:

1{
2 "object": "group_setup_intent",
3 "id": "gsi_jkl012",
4 "status": "processing",
5 "group_quote_intent_id": "gqi_def456"
6}

Creating a Group Setup Intent signals acceptance of the quote. Ensure the employer has explicitly confirmed they want to proceed before making this call.

Important: When a Group Setup Intent is created, all other quotes for this group are automatically rejected. Only one setup can proceed per group.

Step 9: Handle Requirements (if any)

Similar to Group Quote Intents, Group Setup Intents may require additional data. The flow is identical: fetch requirement schemas, collect data, and submit.

Webhook: group_setup_intent.action_required

1{
2 "type": "group_setup_intent.action_required",
3 "data": {
4 "group_setup_intent_id": "gsi_jkl012",
5 "group_id": "gr_abc123",
6 "plan_id": "pl_xyz789",
7 "status": "action_required"
8 }
9}

Step 10: Pending Insurer Approval

After requirements are satisfied, some providers require final approval before activating the policy.

Webhook: group_setup_intent.pending_provider_approval

1{
2 "type": "group_setup_intent.pending_provider_approval",
3 "data": {
4 "group_setup_intent_id": "gsi_jkl012",
5 "group_id": "gr_abc123",
6 "plan_id": "pl_xyz789",
7 "status": "pending_provider_approval"
8 }
9}

Display a holding screen indicating the policy is being finalized with the insurer.

Step 11: Group Policy Activated

Once the provider approves (or if no approval was needed), the group policy is created and activated. If the policy start date is in the future, you’ll receive group_policy.scheduled followed by group_policy.active when coverage begins.

Webhook: group_policy.scheduled (if start date is in the future)

1{
2 "type": "group_policy.scheduled",
3 "data": {
4 "object": "group_policy",
5 "id": "gp_mno345",
6 "status": "scheduled",
7 "employer_id": "er_abc123",
8 "plan": {
9 "id": "pl_xyz789",
10 "name": "Hallesche Premium Health",
11 "provider": {
12 "id": "pr_hallesche",
13 "name": "Hallesche"
14 }
15 },
16 "coverage_start_date": "2024-04-01",
17 "renewal_date": "2025-04-01",
18 "total_monthly_premium": {
19 "amount": "1250.00",
20 "currency": "EUR"
21 },
22 "enrolled_employees": 5
23 }
24}

Webhook: group_policy.active (when coverage begins)

1{
2 "type": "group_policy.active",
3 "data": {
4 "object": "group_policy",
5 "id": "gp_mno345",
6 "status": "active",
7 "employer_id": "er_abc123",
8 "plan": {
9 "id": "pl_xyz789",
10 "name": "Hallesche Premium Health",
11 "provider": {
12 "id": "pr_hallesche",
13 "name": "Hallesche"
14 }
15 },
16 "coverage_start_date": "2024-04-01",
17 "renewal_date": "2025-04-01",
18 "total_monthly_premium": {
19 "amount": "1250.00",
20 "currency": "EUR"
21 },
22 "enrolled_employees": 5
23 }
24}

This completes the employer journey. Display the policy details and provide access to policy documents.

Alternative Outcome: Setup Rejected by Insurer

In some rare cases, the insurer may reject the setup during final review.

Webhook: group_setup_intent.rejected_by_insurer

1{
2 "type": "group_setup_intent.rejected_by_insurer",
3 "data": {
4 "group_setup_intent_id": "gsi_jkl012",
5 "group_id": "gr_abc123",
6 "plan_id": "pl_xyz789",
7 "status": "rejected_by_insurer"
8 }
9}

Webhooks Summary

Your integration should handle these webhooks to drive UI state transitions:

Group Quote Intent Webhooks

EventDescriptionAction
group_quote_intent.processingQuote request is being evaluatedShow loading state
group_quote_intent.action_requiredAdditional data neededDisplay requirement forms
group_quote_intent.awaiting_quoteWaiting for insurer responseShow holding screen
group_quote_intent.quote_availableQuote ready for reviewDisplay quote with accept/reject options
group_quote_intent.rejected_by_insurerInsurer declined to quoteShow rejection message
group_quote_intent.quote_expiredQuote validity period endedPrompt to request new quote

Group Setup Intent Webhooks

EventDescriptionAction
group_setup_intent.processingSetup is being processedShow loading state
group_setup_intent.action_requiredAdditional data neededDisplay requirement forms
group_setup_intent.pending_provider_approvalAwaiting insurer approvalShow holding screen
group_setup_intent.rejected_by_insurerInsurer rejected the setupShow rejection message

Group Policy Webhooks

EventDescriptionAction
group_policy.scheduledPolicy created, coverage starts in futureDisplay policy details with start date
group_policy.activePolicy is now activeDisplay policy details

UI States Reference

Map these states to your employer dashboard:

StateTriggered ByDisplay
BrowsingInitial loadProvider and plan selection
Quote RequestedGroup Quote Intent createdAppropriate holding screens
Action Requiredaction_required webhookRequirement forms with deadline
Awaiting Quoteawaiting_quote webhookHolding screen with expected timeline
Quote Availablequote_available webhookQuote details with accept/reject buttons
Quote Rejectedrejected_by_insurer webhookRejection message with next steps
Setup In ProgressGroup Setup Intent createdLoading indicator
Pending Approvalpending_provider_approval webhookHolding screen
Policy Scheduledgroup_policy.scheduled webhookPolicy details with coverage start date
Policy Activegroup_policy.active webhookPolicy details view

Best Practices

Error Handling

  • Always handle the rejected_by_insurer case for both quotes and setup intents gracefully
  • Provide clear messaging when quotes expire
  • Allow employers to restart the flow if needed

User Experience

  • Display provider response time expectations during the quote waiting period
  • Show requirement deadlines prominently to prevent timeouts
  • Pre-fill requirement forms with any data you already have

Next Steps

Once employers have active group policies, employees can enroll in coverage:

  • Employee enrollment - Employees accept coverage through Enrollment Intents
  • Dependents and beneficiaries - Employees can add family members during enrollment
  • Policy management - Handle renewals, amendments, and cancellations

Resources: