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

# Retrieve Benefit Plan Year

GET https://api.vitablehealth.com/v1/employers/{employer_id}/benefit-plan-years/{benefit_plan_year_id}

Returns one benefit plan year in full — its benefit details plus the per-benefit enrollment rate and SPD link — addressed by its `benefit_plan_year_id`. The caller must be authorized for the employer; an unknown or unauthorized plan year returns 404.

Reference: https://developer.vitablehealth.com/api/employers/get-benefit-plan-year

## Authentication

- `Authorization` header (bearer token, required) — API Key or Access Token authentication using Bearer token in Authorization header. API keys use the vit_apk_ prefix, access tokens use the vit_at_ prefix.

## Servers

- `https://api.vitablehealth.com` (Production, default)
- `https://api.uat.vitablehealth.com` (UAT)

## Request

### Path parameters

- `benefit_plan_year_id` (string, required) — Unique benefit-plan-year identifier (plyr_*).
- `employer_id` (string, required) — Unique employer identifier (empr_*)

### Headers

- `X-Vitable-Organization` (string, optional) — Organization to act as for this request (e.g. `org_SGVsbG8gV29ybGQ`). Optional when your credentials reach a single organization. Required when they reach several — omitting it then returns 400 `organization_required`. A malformed value returns 400 `invalid_organization_header`, and naming an organization you do not have access to returns 403 `organization_access_denied`.

## Response

### 200

- `data` (BenefitPlanYearDetail, required) — The benefit plan year, including its plans and contribution rules.

## Errors

### 400 Bad Request Error

Bad Request - Invalid input or validation error

- `timestamp` (datetime, required) — ISO 8601 timestamp when the error occurred
- `message` (string, required) — Human-readable error message
- `error` (string, required) — Error message (same as message, for backwards compatibility)
- `trace_id` (string, required) — Unique trace ID for debugging and support requests
- `app_error_code` (string, optional, nullable) — Application-specific error code for programmatic handling

### 401 Unauthorized Error

Unauthorized - Invalid or missing API key

- `timestamp` (datetime, required) — ISO 8601 timestamp when the error occurred
- `message` (string, required) — Human-readable error message
- `error` (string, required) — Error message (same as message, for backwards compatibility)
- `trace_id` (string, required) — Unique trace ID for debugging and support requests
- `app_error_code` (string, optional, nullable) — Application-specific error code for programmatic handling

### 403 Forbidden Error

Forbidden - Insufficient permissions for this resource

- `timestamp` (datetime, required) — ISO 8601 timestamp when the error occurred
- `message` (string, required) — Human-readable error message
- `error` (string, required) — Error message (same as message, for backwards compatibility)
- `trace_id` (string, required) — Unique trace ID for debugging and support requests
- `app_error_code` (string, optional, nullable) — Application-specific error code for programmatic handling

### 404 Not Found Error

Resource not found.

- `timestamp` (datetime, required) — ISO 8601 timestamp when the error occurred
- `message` (string, required) — Human-readable error message
- `error` (string, required) — Error message (same as message, for backwards compatibility)
- `trace_id` (string, required) — Unique trace ID for debugging and support requests
- `app_error_code` (string, optional, nullable) — Application-specific error code for programmatic handling

### 409 Conflict Error

Conflict - Resource already exists or conflicts with current state

- `timestamp` (datetime, required) — ISO 8601 timestamp when the error occurred
- `message` (string, required) — Human-readable error message
- `error` (string, required) — Error message (same as message, for backwards compatibility)
- `trace_id` (string, required) — Unique trace ID for debugging and support requests
- `app_error_code` (string, optional, nullable) — Application-specific error code for programmatic handling

### 429 Too Many Requests Error

Too Many Requests - Rate limit exceeded

- `timestamp` (datetime, required) — ISO 8601 timestamp when the error occurred
- `message` (string, required) — Human-readable error message
- `error` (string, required) — Error message (same as message, for backwards compatibility)
- `trace_id` (string, required) — Unique trace ID for debugging and support requests
- `app_error_code` (string, optional, nullable) — Application-specific error code for programmatic handling

### 500 Internal Server Error

Internal Server Error - An unexpected error occurred

- `timestamp` (datetime, required) — ISO 8601 timestamp when the error occurred
- `message` (string, required) — Human-readable error message
- `error` (string, required) — Error message (same as message, for backwards compatibility)
- `trace_id` (string, required) — Unique trace ID for debugging and support requests
- `app_error_code` (string, optional, nullable) — Application-specific error code for programmatic handling

### 502 Bad Gateway Error

Bad Gateway - External service error

- `timestamp` (datetime, required) — ISO 8601 timestamp when the error occurred
- `message` (string, required) — Human-readable error message
- `error` (string, required) — Error message (same as message, for backwards compatibility)
- `trace_id` (string, required) — Unique trace ID for debugging and support requests
- `app_error_code` (string, optional, nullable) — Application-specific error code for programmatic handling

## Types

### BenefitPlanYearDetail

One plan year, detail view. Standalone (no shared base) so the exact detail payload is readable in one place; the list serializer is a separate class even where fields overlap. Detail carries the SPD link and omits the list-only ``is_current`` flag.

- `benefit_plan_year_id` (string, required) — Prefixed plan-year identifier (`plyr_*`).
- `benefit_id` (string, required) — Prefixed benefit identifier (`bprd_*`).
- `year` (integer, required) — Calendar coverage year.
- `family` (enum, required) — Benefit family. * `mec` - Mec * `mvp` - Mvp * `ichra` - Ichra * `vpc` - Vpc * `dental` - Dental * `vision` - Vision
  - Allowed values: `mec`, `mvp`, `ichra`, `vpc`, `dental`, `vision`
- `product_name` (string, required) — Benefit/product display name.
- `carrier` (string, required, nullable) — Carrier name, or null (e.g. ICHRA).
- `network_names` (list of string, required) — Displayed networks: ["multi"] for ICHRA, otherwise the plan year's distinct network names.
- `premium_in_cents` (integer, required, nullable) — Monthly premium in cents; only for an ICHRA benefit with effective coverage.
- `employee_contribution` (ContributionRange, required, nullable) — Employee contribution range.
- `employer_contribution` (ContributionRange, required, nullable) — Employer contribution range.
- `status` (enum, required) — `active`/`upcoming`/`inactive`. * `active` - Active * `upcoming` - Upcoming * `open_enrollment` - Open Enrollment * `inactive` - Inactive
  - Allowed values: `active`, `upcoming`, `open_enrollment`, `inactive`
- `coverage_start` (date, required) — Coverage start.
- `coverage_end` (date, required, nullable) — Coverage end.
- `open_enrollment_start` (date, required) — Open-enrollment start.
- `open_enrollment_end` (date, required, nullable) — Open-enrollment end.
- `offered_states` (list of string, required) — Distinct offered state codes.
- `enrollment_rate` (EnrollmentRate, required) — Enrolled/eligible rate for this plan year.
- `spd_file_url` (string, required, nullable) — Summary Plan Description (SPD) link, or null.
- `contribution_strategy` (_ContributionStrategy, required) — How the plan year prices contributions (ICHRA classes or coverage tiers).
- `eligibility_policy` (_EligibilityPolicy, required, nullable) — The plan year's active eligibility policy, or null.

### ContributionRange

- `min_cents` (integer, required) — Lowest per-tier contribution in cents.
- `max_cents` (integer, required) — Highest per-tier contribution in cents.

### EnrollmentRate

- `eligible` (integer, required) — Employees eligible for this plan year.
- `enrolled` (integer, required) — Employees enrolled in this plan year.
- `percentage` (integer, required) — ``enrolled / eligible`` whole-number percent (0 when none).

### _ContributionStrategy

How the plan year prices contributions: exactly one collection is populated, determined by the plan year's ``family`` (ICHRA vs tier-priced).

- `ichra_contribution_classes` (list of _IchraContributionClass, required) — ICHRA contribution classes; empty for tier-priced benefits.
- `contribution_tiers` (list of _ContributionTier, required) — Coverage tiers and their costs; empty for ICHRA benefits.

### _EligibilityPolicy

The plan year's active eligibility policy. Mirrors the internal ``PlanYearEligibilityPolicyDTO`` but exposes only the public subset (no raw ids, ``active_in``, or timestamps).

- `waiting_period` (enum, required, nullable) — When coverage starts for a newly-eligible member, or null. * `FIRST_OF_FOLLOWING_MONTH` - First Of Following Month * `THIRTY_DAYS` - Thirty Days * `SIXTY_DAYS` - Sixty Days
  - Allowed values: `FIRST_OF_FOLLOWING_MONTH`, `THIRTY_DAYS`, `SIXTY_DAYS`
- `termination_rule` (enum, required) — When coverage ends after a member becomes ineligible. * `END_OF_N_MONTHS` - End Of N Months * `END_OF_PLAN_YEAR` - End Of Plan Year
  - Allowed values: `END_OF_N_MONTHS`, `END_OF_PLAN_YEAR`
- `termination_n_months` (integer, required, nullable) — Months of continued coverage; set only when `termination_rule` is `END_OF_N_MONTHS`.
- `rules` (list of _EligibilityPolicyRule, required) — Eligibility rules; never empty for a valid policy.

### _IchraContributionClass

One ICHRA contribution class, mirroring the internal configuration endpoint's ``IchraContributionClassConfigurationEntitySerializer`` field-for-field. Two deliberate differences for the public surface: the identifier is the opaque prefixed ``iccl_*`` form rather than a raw UUID, and the matcher choices come from the domain enums rather than the model's.

- `contribution_class_id` (string, required) — Prefixed contribution-class identifier (`iccl_*`).
- `amount_in_cents` (integer, required) — Monthly allowance in cents.
- `employment` (enum, required) — Employment matcher. * `Unspecified` - Unspecified * `Full Time` - Full Time * `Part Time` - Part Time * `Temporary` - Temporary * `Seasonal` - Seasonal
  - Allowed values: `Unspecified`, `Full Time`, `Part Time`, `Temporary`, `Seasonal`
- `compensation` (enum, required) — Compensation matcher. * `Unspecified` - Unspecified * `Salary` - Salary * `Hourly` - Hourly
  - Allowed values: `Unspecified`, `Salary`, `Hourly`
- `location` (enum, required) — Location matcher type. * `Unspecified` - Unspecified * `State` - State
  - Allowed values: `Unspecified`, `State`
- `location_value` (string, required, nullable) — Location matcher value (CSV of state codes), or null.
- `family_status` (enum, required) — Family-status matcher. * `Unspecified` - Unspecified * `EE` - Ee * `ES` - Es * `EC` - Ec * `EF` - Ef
  - Allowed values: `Unspecified`, `EE`, `ES`, `EC`, `EF`
- `min_age` (integer, required, nullable) — Age-band lower bound, or null.
- `max_age` (integer, required, nullable) — Age-band upper bound, or null.

### _ContributionTier

One non-ICHRA coverage tier, mirroring the internal configuration ``CompanyBenefitPlanTierCostDTO`` minus the tier-cost id, ``pepm`` and ``pepm_per_dependent``; ``benefit_plan_id`` is the prefixed ``bpln_*`` form rather than a raw UUID.

- `benefit_plan_id` (string, required) — Prefixed benefit-plan identifier (`bpln_*`).
- `benefit_plan_name` (string, required) — Benefit plan name.
- `benefit_plan_tier_name` (string, required) — Coverage-tier name.
- `coverage_tier` (enum, required) — Coverage-tier code for this tier. * `Unspecified` - Unspecified * `EE` - Ee * `ES` - Es * `EC` - Ec * `EF` - Ef
  - Allowed values: `Unspecified`, `EE`, `ES`, `EC`, `EF`
- `cost` (integer, required) — Monthly employee deduction in cents.
- `cost_per_dependent` (integer, required) — Monthly employee deduction per dependent, in cents.
- `dependents_required_in` (boolean, required) — Whether dependents are required for this tier.
- `spouse_required_in` (boolean, required) — Whether a spouse is required for this tier.

### _EligibilityPolicyRule

One eligibility rule — the workforce slice it makes eligible. Mirrors the internal ``PlanYearEligibilityPolicyRuleDTO`` minus the raw ids and timestamps.

- `employee_class` (enum, required) — Employee-class matcher (`All` for any). * `Full Time` - Full Time * `Part Time` - Part Time * `Temporary` - Temporary * `Intern` - Intern * `Seasonal` - Seasonal * `Individual Contractor` - Individual Contractor * `All` - All
  - Allowed values: `Full Time`, `Part Time`, `Temporary`, `Intern`, `Seasonal`, `Individual Contractor`, `All`
- `compensation_type` (enum, required) — Compensation matcher (`All` for any). * `Salary` - Salary * `Hourly` - Hourly * `All` - All
  - Allowed values: `Salary`, `Hourly`, `All`
- `eligible_geographical_location` (_EligibleGeographicalLocation, required) — Geographic matcher.

### _EligibleGeographicalLocation

- `type` (enum, required) — `All` (unrestricted) or `StateCodes` (restricted to `state_codes`). * `All` - All * `StateCodes` - State Codes
  - Allowed values: `All`, `StateCodes`
- `state_codes` (list of string, required) — States the rule is restricted to; empty when `type` is `All`.

## Examples

**Response**

```json
{
  "data": {
    "benefit_plan_year_id": "string",
    "benefit_id": "string",
    "year": 1,
    "family": "mec",
    "product_name": "string",
    "carrier": "string",
    "network_names": [
      "string"
    ],
    "premium_in_cents": 1,
    "employee_contribution": {
      "min_cents": 1,
      "max_cents": 1
    },
    "employer_contribution": {
      "min_cents": 1,
      "max_cents": 1
    },
    "status": "active",
    "coverage_start": "2023-01-15",
    "coverage_end": "2023-01-15",
    "open_enrollment_start": "2023-01-15",
    "open_enrollment_end": "2023-01-15",
    "offered_states": [
      "string"
    ],
    "enrollment_rate": {
      "eligible": 1,
      "enrolled": 1,
      "percentage": 1
    },
    "spd_file_url": "string",
    "contribution_strategy": {
      "ichra_contribution_classes": [
        {
          "contribution_class_id": "string",
          "amount_in_cents": 1,
          "employment": "Unspecified",
          "compensation": "Unspecified",
          "location": "Unspecified",
          "location_value": "string",
          "family_status": "Unspecified",
          "min_age": 1,
          "max_age": 1
        }
      ],
      "contribution_tiers": [
        {
          "benefit_plan_id": "string",
          "benefit_plan_name": "string",
          "benefit_plan_tier_name": "string",
          "coverage_tier": "Unspecified",
          "cost": 1,
          "cost_per_dependent": 1,
          "dependents_required_in": true,
          "spouse_required_in": true
        }
      ]
    },
    "eligibility_policy": {
      "waiting_period": "FIRST_OF_FOLLOWING_MONTH",
      "termination_rule": "END_OF_N_MONTHS",
      "termination_n_months": 1,
      "rules": [
        {
          "employee_class": "Full Time",
          "compensation_type": "Salary",
          "eligible_geographical_location": {
            "type": "All",
            "state_codes": [
              "string"
            ]
          }
        }
      ]
    }
  }
}
```

**SDK Code**

```python
import requests

url = "https://api.vitablehealth.com/v1/employers/empr_abc123def456/benefit-plan-years/plyr_abc123def456"

headers = {"Authorization": "Bearer <apiKey>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.vitablehealth.com/v1/employers/empr_abc123def456/benefit-plan-years/plyr_abc123def456';
const options = {method: 'GET', headers: {Authorization: 'Bearer <apiKey>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.vitablehealth.com/v1/employers/empr_abc123def456/benefit-plan-years/plyr_abc123def456"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <apiKey>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.vitablehealth.com/v1/employers/empr_abc123def456/benefit-plan-years/plyr_abc123def456")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <apiKey>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.vitablehealth.com/v1/employers/empr_abc123def456/benefit-plan-years/plyr_abc123def456")
  .header("Authorization", "Bearer <apiKey>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.vitablehealth.com/v1/employers/empr_abc123def456/benefit-plan-years/plyr_abc123def456', [
  'headers' => [
    'Authorization' => 'Bearer <apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.vitablehealth.com/v1/employers/empr_abc123def456/benefit-plan-years/plyr_abc123def456");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <apiKey>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <apiKey>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.vitablehealth.com/v1/employers/empr_abc123def456/benefit-plan-years/plyr_abc123def456")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```