> For the complete documentation index, see [llms.txt](https://docs.ichra.healthsherpa.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ichra.healthsherpa.com/integration-guide/integration-scenarios.md).

# Integration Scenarios

These scenarios walk through the real operations an ICHRA platform performs — from quoting an employee's first plan options through managing their policy years later — and the exact API calls needed for each.

For the high-level integration flow, see [Use Cases](https://docs.ichra.healthsherpa.com/integration-guide/use-cases). For enrollment routing logic, see [Enrollment Decision Path](https://docs.ichra.healthsherpa.com/integration-guide/enrollment-decision-path).

Two principles apply across every scenario:

**Always build both enrollment paths.** EnrollConnect is the preferred path — it gives your platform full lifecycle control. But not every carrier supports it today. Platforms should always implement the Deeplink fallback so that every enrollable plan has a path to coverage. The upcoming Deeplink V2 endpoint accepts the same canonical payload as EnrollConnect, making it straightforward to support both. The current V1 Deeplink uses a different field structure but carries the same data.

**Always include your API key and externalid.** Pass the `x-api-key` header on every EnrollConnect call (required). For Deeplink, the API key is optional in production but recommended — it enables automatic `_agent_id` allow-listing. In staging, the Deeplink endpoint uses Basic Auth instead (contact your onboarding representative for credentials). Set `external_id` on every application you create — it is your only reliable correlation key for Deeplink enrollments, where you do not receive an `application_id` at creation time. Webhooks include `external_id` regardless of which path was taken, making it the bridge between your records and HealthSherpa.

### Plan Shopping

#### Scenario 1 — Quoting an Individual Applicant

> An employee needs to see available plans and prices for their household.

**1. Get quotes**

```
POST /api/v1/quotes
```

```json
{
  "zip_code": "75201",
  "fip_code": "48113",
  "applicants": [
    { "age": 35, "relationship": "primary", "smoker": false },
    { "age": 33, "relationship": "spouse", "smoker": false },
    { "age": 5, "relationship": "child", "smoker": false }
  ],
  "off_ex": true,
  "per_page": 50,
  "plan_year": 2026
}
```

The response returns an array of plans with premiums. Each plan includes two flags that determine what you can do with it:

<table><thead><tr><th width="249.5">Flag</th><th>Meaning</th></tr></thead><tbody><tr><td><code>api_enrollment: true</code></td><td>Enrollable via EnrollConnect API</td></tr><tr><td><code>deeplink_enrollment: true</code></td><td>Enrollable via Deeplink (HealthSherpa UI)</td></tr><tr><td>Both <code>false</code></td><td>Not enrollable via HealthSherpa (quote-only)</td></tr></tbody></table>

Store these flags alongside each quoted plan. You will need them when the employee selects a plan and is ready to enroll.

**2. Control what data comes back**

Several optional flags expand the quote response:

<table><thead><tr><th width="156.5">Flag</th><th width="108">Default</th><th>What it adds</th></tr></thead><tbody><tr><td><code>off_ex: true</code></td><td><code>false</code></td><td><strong>Required.</strong> Returns only off-exchange plans. Without this, the API returns on-exchange plans which are not supported for ICHRA enrollment.</td></tr><tr><td><code>include_non_enrollable_offex: true</code></td><td><code>false</code></td><td>Includes off-exchange plans that are not enrollable through HealthSherpa. These plans will have both <code>api_enrollment</code> and <code>deeplink_enrollment</code> set to <code>false</code>. Useful if your platform wants to show the full market.</td></tr><tr><td><code>all_benefits: true</code></td><td><code>false</code></td><td>Returns the full SBC benefit list for each plan. The default response includes a curated subset of commonly compared benefits.</td></tr><tr><td><code>all_details: true</code></td><td><code>false</code></td><td>Includes <code>cost_sharing_tiers</code> with per-tier (in-network, in-network tier 2, out-of-network) deductible, MOOP, and coinsurance breakdowns.</td></tr><tr><td><code>add_attributes: true</code></td><td><code>false</code></td><td>Includes an <code>attributes</code> array on each plan with comparative rankings (<code>good</code>, <code>bad</code>, <code>avg</code>) across cost and feature categories like deductible, MOOP, primary care cost, and specialist cost. Pair with <code>utilization</code> (<code>low</code>, <code>medium</code>, <code>high</code>) to adjust rankings based on expected healthcare usage.</td></tr></tbody></table>

You can also use the `filter` parameter to limit which fields are returned per plan, reducing payload size when you only need specific data (e.g., `filter: "hios_id,premium,metal_level,api_enrollment,deeplink_enrollment"`). The fields `hios_id`, `name`, `year`, and `gross_premium` are always included regardless of filter.

**3. Get carrier-specific requirements before building the enrollment form**

Once the user selects a plan, call the plan lookup to retrieve the carrier's enrollment requirements:

```
GET /api/v1/plans/{hios_id}?plan_year=2026&include=enrollment_requirements
```

The `enrollment_requirements` object in the response contains:

* **Attestation text** that must be presented to the consumer or agent during enrollment. Each carrier files specific legal language with their state DOI. You must display the exact `content` text from the response as the checkbox labels in your enrollment form. Do not use generic labels like "I agree to the issuer attestations." Only render attestation fields that are present in the response; absent or null keys are not required for that carrier.
* **SEP event types** the carrier accepts, along with whether each requires supporting documentation (`documentation_required`).
* **Marital status options** if the carrier requires a granular marital status field instead of a simple boolean.

The attestation keys map directly to fields in the enrollment request:

| Response Key                         | Maps to                                                    |
| ------------------------------------ | ---------------------------------------------------------- |
| `agrees_issuer_attestations`         | `attestations.agrees_issuer_attestations`                  |
| `electronic_signature_consent`       | `attestations.electronic_signature_consent`                |
| `broker_signature_attestation`       | `attestations.broker_signature_attestation`                |
| `pediatric_dental`                   | `attestations.pediatric_dental` (string enum, not boolean) |
| `state_supplement_primary_signature` | `signatures.state_supplement_primary_signature`            |

The `electronic_signature_consent` content may contain `%{signature_name}`. Replace this placeholder with the applicant's full legal name before displaying the text.

Call this endpoint once when the user selects a plan. Cache the result for the duration of the enrollment session.

***

#### Scenario 2 — Quoting an Employer Group

> A broker or employer needs plan and pricing data across multiple employees to model contribution strategy.

**1. Quote each employee household**

Call `POST /api/v1/quotes` once per employee, varying the inputs:

```json
{
  "zip_code": "46204",
  "fip_code": "18097",
  "applicants": [{ "age": 28, "relationship": "primary", "smoker": false }],
  "off_ex": true,
  "plan_year": 2026
}
```

Each employee has a different age, zip code, and household composition. The API returns plans and premiums specific to that employee's location and demographics.

**2. Model affordability**

From each response, identify the lowest-cost silver plan (LCSP). Use it as the benchmark for IRS affordability calculations. Vary the `hra.amount` across quote calls to compare contribution scenarios by employee class and geography.

**3. Note the carrier mix**

Across the group, different employees will see different carriers depending on their location. Some carriers will be `api_enrollment: true`, others `deeplink_enrollment: true` only. This affects the enrollment experience you can offer per employee — plan for both paths.

No applications are created during this phase. This is purely plan data and pricing.

***

### Enrollment

#### Scenario 3 — Enrolling a Single Member

> An employee has selected a plan and is ready to enroll.

**1. Collect the enrollment data**

Before building your enrollment form, make sure you have called `GET /api/v1/plans/{hios_id}?plan_year=2026&include=enrollment_requirements` (Scenario 1, step 3). The attestation text from that response must be displayed to the user, and the attestation fields you include in the payload must match what the carrier requires.

Gather the member's information into a structured payload. The example below shows the EnrollConnect schema. The Deeplink V1 endpoint uses a flatter field structure (see note in step 3b), but the data is the same:

```json
{
  "external_id": "your-internal-employee-id",
  "plan_hios_id": "54192IN0010037",
  "plan_year": 2026,
  "applicants": {
    "primary": {
      "first_name": "Jane",
      "last_name": "Smith",
      "date_of_birth": "1990-06-15",
      "gender": "female",
      "email": "jane.smith@example.com",
      "phone": "3175551234",
      "phone_type": "cell",
      "us_citizen": true,
      "resides_in_state": true,
      "uses_tobacco": false,
      "race_ethnicity": "decline_to_answer",
      "language_spoken": "english",
      "language_written": "english",
      "signature": "Jane Smith"
    }
  },
  "residential_address": {
    "street_address_1": "456 Oak Ave",
    "city": "Indianapolis",
    "state": "IN",
    "zip_code": "46204",
    "fips_code": "18097"
  },
  "special_enrollment_period": {
    "event_type": "offered_ichra",
    "event_date": "2026-01-01"
  },
  "hra": {
    "offered_hra": true,
    "type": "ichra",
    "amount": 500,
    "contribution_covers": "premium",
    "start": "2026-01-01",
    "employer": {
      "name": "Acme Corp",
      "fein": "123456789"
    }
  },
  "signatures": {
    "signature_date": "2026-05-21"
  },
  "attestations": {
    "electronic_signature_consent": true
  }
}
```

**2. Route to the correct endpoint**

Check the enrollment flags you stored at quote time:

```
if plan.api_enrollment:
    POST /api/v1/applications          → EnrollConnect
elif plan.deeplink_enrollment:
    POST /public/ichra/off_ex          → Deeplink
else:
    not enrollable via HealthSherpa
```

Include `x-api-key` in the header for EnrollConnect (required). For Deeplink, include it in production (recommended) or use Basic Auth in staging.

**3a. EnrollConnect path**

```
POST /api/v1/applications
```

You receive an `application_id` immediately in the response, along with an `errors` array showing what is missing or invalid, and a `next_actions` array listing the operations available in the current state (e.g., `update`, `submit`, `upload_supporting_documentation`).

Iterate:

1. Read the `errors` array
2. Fix the issues via `PUT /api/v1/applications/{id}`
3. Repeat until `errors` is empty

Then submit:

```
POST /api/v1/applications/{id}/submit
```

Returns `202 Accepted`. The submission is asynchronous — `policy_status` moves to `pending_effectuation` on success, or `submission_failed` if the carrier rejects it. Check `payment_instructions` in the response to determine the payment path (see Scenario 5 — OEP vs. SEP for document handling, and the payment section below).

**3b. Deeplink path**

```
POST /public/ichra/off_ex
```

The Deeplink V1 endpoint requires `_agent_id` (your HealthSherpa agent slug) and uses top-level flat fields for some data (e.g., `street_address`, `zip_code`, `sep_reason` instead of `residential_address.street_address_1` and `special_enrollment_period.event_type`). Refer to the [Application Deeplink API](https://docs.ichra.healthsherpa.com/api-reference/endpoints/application-deeplink-api) for the full field mapping.

You receive a `302` redirect. Capture the `Location` header from your backend and redirect the user's browser to the HealthSherpa enrollment UI. The user completes enrollment there. This call must be made server-side — do not call it from the browser directly.

You do **not** receive an `application_id` from this call. Your `external_id` is the only way to match this enrollment back to your records. When the enrollment is submitted, webhooks will fire with both `external_id` and the HealthSherpa `application_id`.

**4. Handle payment (EnrollConnect)**

After submission, check `payment_instructions` on the application response:

| Field                                    | Meaning                                                                                                                                                       |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `payment_redirect_supported: true`       | Use `GET /api/v1/applications/{id}/payment_redirect` to get the carrier's payment URL and form fields. Build a form POST and submit it in the user's browser. |
| `payment_required_with_submission: true` | Payment must be set via `PUT /api/v1/applications/{id}/payment_method` before calling submit. Currently applies to Cigna and Elevance.                        |
| `pay_by_phone_supported: true`           | Display the `payment_phone_number` for the member to call.                                                                                                    |

**Why build both paths:** Carrier support for EnrollConnect is expanding, but today some carriers are Deeplink-only. If your platform only implements EnrollConnect, those plans become dead ends for your users. Building the Deeplink fallback ensures every enrollable plan has a path to coverage.

***

#### Scenario 4 — Enrolling a Whole Company

> All employees have made plan selections. The platform needs to submit enrollments in bulk.

**1. Route each employee to the correct path**

For each employee's selected plan, check `api_enrollment` and `deeplink_enrollment` per Scenario 3. In a typical employer group, the mix will be split — some employees go through EnrollConnect, others through Deeplink, depending on the carrier.

**2. Set `external_id` on every application**

This is critical for batch operations. For EnrollConnect apps you get `application_id` back immediately. For Deeplink apps, `external_id` is your only handle until the first webhook arrives.

```json
{
  "external_id": "emp-00142-2026",
  ...
}
```

**3. Submit EnrollConnect apps programmatically**

Create and submit applications in sequence:

```
for each employee where api_enrollment is true:
    POST /api/v1/applications
    PUT  /api/v1/applications/{id}   (iterate until errors empty)
    POST /api/v1/applications/{id}/submit
```

**4. Generate Deeplink URLs for the rest**

For employees whose selected plan is Deeplink-only:

```
for each employee where deeplink_enrollment is true:
    POST /public/ichra/off_ex
    capture Location header
    send redirect URL to employee (email, in-app link, etc.)
```

The employee completes enrollment in HealthSherpa's UI.

**5. Track the batch via webhooks**

Subscribe to Submission Confirmation and Policy Status webhooks. Every webhook payload includes `external_id`, so you can match events back to your employee records regardless of which enrollment path was used.

This is far more efficient than polling each application individually, especially for large groups.

***

#### Scenario 5 — OEP vs. SEP Enrollment

> The enrollment experience differs depending on whether it is Open Enrollment Period or a Special Enrollment Period. This applies to both EnrollConnect and Deeplink paths.

**Open Enrollment Period (OEP)**

During OEP, `special_enrollment_period` is not required in the application payload. The system determines OEP eligibility based on the current date. Simply omit the `special_enrollment_period` object.

**Special Enrollment Period (SEP)**

Outside OEP, every enrollment requires a qualifying life event:

```json
{
  "special_enrollment_period": {
    "event_type": "offered_ichra",
    "event_date": "2026-01-01"
  }
}
```

For ICHRA enrollments, `offered_ichra` is the most common reason. The carrier validates that the event date is within the allowed window (typically 60 days before or after).

**Check what the carrier requires**

Use the plan lookup to see which SEP event types a carrier accepts:

```
GET /api/v1/plans/{hios_id}?plan_year=2026&include=enrollment_requirements
```

The `special_enrollment_period.event_types` object in the response lists each accepted event type with:

* `event_date_days_before` / `event_date_days_after` — the valid date window
* `documentation_required` — whether the carrier requires supporting documents for this SEP reason

**Handling SEP documentation**

After creating an application with a SEP reason, check `document_status` in the response:

| Status        | Action                                                                                                                                                                      |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `none_needed` | No documentation required. Submit directly.                                                                                                                                 |
| `required`    | Upload documents via `POST /api/v1/applications/{id}/supporting_documentation` before or after submission. The carrier will hold effectuation until documents are verified. |

Track `document_status` transitions: `required` → `uploaded` → `verified` (or `denied`). Poll the application or subscribe to webhooks to detect changes.

Document upload is only available via EnrollConnect. For Deeplink enrollments, the user uploads documents through the HealthSherpa UI after redirect.

***

### Policy Lifecycle

#### Scenario 6 — Tracking Member Data Through the Policy Lifecycle

> Keep your system in sync with HealthSherpa and carrier-side changes throughout the life of a policy.

**Polling**

```
GET /api/v1/applications/{id}
```

Returns the current state of everything: `policy_status`, `document_status`, `payment`, `policies` (with effective/expiration dates and member roster), `issuer_member_id` (the carrier-assigned member identifier), and the `events` audit trail.

Polling guidance: wait at least 1 hour after submission before the first poll, then poll every 4–8 hours. Most carriers report effectuation within 1–3 business days. Do not poll more frequently than once per minute per application. Pass `include_events=false` to skip the events timeline and reduce response size when you only need status fields.

**Webhooks**

| Webhook                 | Fires when                                          | Key fields                                                                                  |
| ----------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Submission Confirmation | Application is submitted to the carrier             | `application_id`, `external_id`, `policy_status`, `members`, `policies`                     |
| Policy Status           | Status changes (effectuated, cancelled, terminated) | `application_id`, `external_id`, `policy_status`, `issuer_member_id`, `payment`, `policies` |

Both webhook types include `external_id`. This is how you correlate Deeplink enrollments back to your records — you may not have the `application_id` until the first webhook arrives. Store it at that point for future polling.

The `issuer_member_id` field (on both the webhook payload and the GET response) is the carrier-assigned member identifier. Use it to cross-reference members with carrier systems. It is `null` until the carrier reports it.

**Events timeline**

The `events` array on the application is an audit trail of all changes:

* `submitted` — carrier submission with response code
* `changed` — field-level changes with before/after values
* `document_status_changed` — SEP document status transitions
* `policy_status_updated` — policy status transitions
* `cancelled` / `submission_failed` — terminal or error events

Events are ordered newest-first. Maximum 50 events returned per request.

**Payment tracking**

The `payment` object on each policy includes:

* `payment_status` — carrier-reported payment state (`unpaid_binder`, `paid_binder`, `paid`, `past_due`)
* `paid_through_date` — how far the member has paid
* `grace_period_start_date` — when grace period began (if applicable)
* `current_member_responsibility_balance_due` — outstanding balance
* `autopay_indicator` — whether automatic payment is enabled

***

#### Scenario 7 — Cancelling or Terminating a Member Policy

> A member needs to end their coverage.

Check `policy_status` on the application to determine which action to take:

| Current status         | Action                          | Endpoint                                   |
| ---------------------- | ------------------------------- | ------------------------------------------ |
| `pending_effectuation` | Cancel (coverage never started) | `POST /api/v1/applications/{id}/cancel`    |
| `effectuated`          | Terminate (coverage was active) | `POST /api/v1/applications/{id}/terminate` |

Both return `202 Accepted` — the operation is asynchronous. Poll the application or use webhooks to confirm the final status transition to `cancelled` or `terminated`. These are terminal states.

Cancel and terminate are EnrollConnect endpoints, but they work on any application — including those that originated from Deeplink. Once you have the `application_id` (from the first webhook), you can manage the full lifecycle via the API.

***

#### Scenario 8 — Adding a Dependent During a Policy

> A member has a qualifying life event (birth, marriage, adoption) and needs to add a dependent to their active policy.

**1. Check that the carrier supports changes**

```
GET /api/v1/applications/{id}
```

Confirm `supports_changes: true` and `can_report_change: true`.

**2. Update the application**

```
PUT /api/v1/applications/{id}
```

Add the new dependent to `applicants.dependents` and update the `special_enrollment_period` with the qualifying event:

```json
{
  "applicants": {
    "dependents": [
      {
        "first_name": "Alex",
        "last_name": "Smith",
        "date_of_birth": "2026-05-01",
        "gender": "male",
        "relationship": "child"
      }
    ]
  },
  "special_enrollment_period": {
    "event_type": "birth",
    "event_date": "2026-05-01"
  }
}
```

**3. Resubmit**

```
POST /api/v1/applications/{id}/submit
```

The `events` timeline will record the change. Webhooks fire on status transitions. This is an EnrollConnect-only operation.

***

#### Scenario 9 — Changing Plan Selection During Open Enrollment

> A member wants to switch plans during OEP.

**1. Check eligibility**

```
GET /api/v1/applications/{id}
```

Confirm `can_change_plan: true`.

**2. Same issuer — update in place**

If the new plan is from the same carrier:

```
PUT /api/v1/applications/{id}
```

```json
{
  "plan_hios_id": "54192IN0010042"
}
```

Then resubmit:

```
POST /api/v1/applications/{id}/submit
```

During OEP, `special_enrollment_period` is not required.

**3. Different issuer — cancel and re-enroll**

The application is tied to a carrier. Cross-issuer plan changes via `PUT` are not supported. Instead:

1. Cancel or terminate the existing application:
   * `POST /api/v1/applications/{id}/cancel` if `pending_effectuation`
   * `POST /api/v1/applications/{id}/terminate` if `effectuated`
2. Create a new application with the new plan via Scenario 3

Check the new plan's enrollment flags — if the member is switching to a carrier that is Deeplink-only, the new enrollment goes through that path.

***

#### Scenario 10 — Updating Demographic Information During the Policy

> A member moved, changed their phone number, or needs to correct a name.

**1. Check that updates are accepted**

```
GET /api/v1/applications/{id}
```

Confirm `supports_changes: true` and `can_report_change: true`.

**2. Resend the full application with your changes**

`PUT` is a full replacement, not a partial update — resend the complete application (same shape as create) with the changed value(s) applied. Any field you omit is cleared, and any current dependent not included in `applicants.dependents` is removed (the request also fails if `applicants.primary` is missing).

```
PUT /api/v1/applications/{id}
```

```json
{
  "plan_year": 2026,
  "plan_hios_id": "12345IN0010001",
  "residential_address": {
    "street_address_1": "789 New St",
    "city": "Fort Wayne",
    "state": "IN",
    "zip_code": "46802",
    "fips_code": "18003"
  },
  "applicants": {
    "primary": { "...": "all current primary fields, unchanged" },
    "dependents": [ { "...": "every current dependent, unchanged" } ]
  }
}
```

**3. Resubmit**

```
POST /api/v1/applications/{id}/submit
```

**Identity protection rules**

After submission, certain changes are restricted to prevent identity fraud:

* Cannot change `first_name` and `last_name` in the same request — correct one at a time
* Cannot change `date_of_birth` in the same request as a name change

Individual field corrections are always accepted.

**Address changes**

An address change may place the member in a new service area. The API validates that the current plan is still available at the new location. If not, the member will need to select a new plan (see Scenario 9).

Changes are tracked in the `events` timeline with before/after values. This is an EnrollConnect-only operation.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ichra.healthsherpa.com/integration-guide/integration-scenarios.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
