Managing benefits
Benefits API Integration Guide
1. Overview
The Benefits API enables you to manage benefits for EOR and Contractor engagements through Oyster's platform.
This guide covers the end-to-end workflow for benefits management — from listing available offerings to provisioning packages, tracking enrollments, and requesting additional benefits.
Note for Reseller partners using the API - you will need to use the X-Oyster-Customer-Id header for all of the calls in this guide. For more information, see the Reseller guide to using the Oyster API.
2. Benefits Process Flow
flowchart TD
A[List Available Benefits] --> B[Provision Benefits]
B --> C[View Engagement Benefits]
C --> D[Track Enrollments]
E[Request Additional Benefits] --> B
subgraph States
S1[REQUESTED]
S2[APPLIED]
S3[IN_PROGRESS]
S4[ENROLLED]
S5[PROVISIONED]
S6[CANCELED]
S7[FINISHED]
S8[OPTED_OUT]
end
S1 --> S2 --> S3 --> S4
S4 --> S6
S4 --> S7
S4 --> S8
S5 --> S2
3. Key Concepts & Terminology
Before calling the endpoints, it is critical to understand how Oyster structures benefits.
A. Packages vs. Supplementary Benefits
Our platform structures benefits into two distinct categories:
| Feature | Benefit Packages | Supplementary Benefits |
|---|---|---|
| Definition | Comprehensive bundles (e.g., "Essential", "Competitive"). | Standalone, discretionary plans (e.g., Pet Insurance). |
| Composition | Typically combines statutory (mandatory) items with core health/insurance. | Single-focus plans. |
| Rules | Required in some regions (e.g., Spain). You can select only one package. | Optional. You can select multiple supplementary plans. |
| Visibility | A specific benefit plan cannot exist simultaneously as a package component and a supplementary option. | |
| Fee | A package has only one fee component that covers multiple plans. | Supplementary plans have fee component attached to each plan. |
B. The Lifecycle (Status Definitions)
The benefits status changes as the employee moves through their onboarding journey:
PROVISIONED: You have selected the benefits configuration via the API, but the Team Member (TM) is not yet fully "engaged" (onboarded) in the Oyster system.APPLIED: The TM has become engaged. The system has locked the configuration and sent the request to our internal operations team.ENROLLED: Success. Oyster has communicated with the insurance provider, and the provider has confirmed the enrollment on their system.
C. Contribution Logic & The "Cost Split" Rule
When defining employer_contributions, you can cap the number of dependents you are willing to support using max_dependents_covered.
- Scenario: You set
max_dependents_covered: 4. - Result:
- Dependents 1–4: Covered by the employer (at the % or amount defined in the payload).
- Dependent 5+: The Team Member pays 100% of the cost for these additional dependents.
4. Integration Workflow
Step 1: List Available Benefits
Before provisioning any benefits, first check what benefits are available for your engagement using Retrieve an engagement's available benefits offerings.
When to use
- Before provisioning any benefits
- When reviewing available options for an engagement
- To get plan IDs needed for provisioning
Endpoint
GET /v0.1/benefits/engagements/{engagement_id}/offerings
Description
Returns all available benefit packages and supplementary plans for a specific engagement. This endpoint provides the catalog of benefits that can be provisioned, including pricing, provider information, and contribution rules.
Path Parameters
engagement_id(required, string): The unique identifier of the engagement
Response Fields
data.packages[]: Array of available benefit packagesid: Package unique identifier (used for provisioning)name: Package display namefee: Package-level fee (covers all plans in the package)decimal: Fee amount as stringcurrencyCode: ISO 4217 currency code
plans[]: Array of plans included in this packageid: Plan unique identifiername: Plan display nameproviderName: Insurance/benefit provider namecategory: Plan type (HEALTH, PENSION, LIFE, etc.)link: External provider information URLguideUrl: Plan guide/documentation URLestimatedPrice: Estimated monthly cost per persondecimal: Amount as stringcurrencyCode: ISO 4217 currency code
data.supplementary.plans[]: Array of standalone supplementary plans- Contains same fields as package plans, plus:
fee: Per-plan fee (supplementary plans have individual fees)costSplitAllowed: Whether employer can split costs with employeedependentsConfig: ALLOWED, REQUIRED, or NOT_ALLOWEDmaxDependents: Maximum number of dependents allowed (0 = unlimited)minEmployerContribution: Minimum employer contribution percentagesupportedEngagementTypes: Array of engagement types (EMPLOYMENT, CONTRACT)supportedEorTypes: Array of EOR types (DIRECT, INDIRECT)
Example Request
curl --request GET \\
--url <https://api.oysterhr.com/v0.1/benefits/engagements/ENGAGEMENT_ID/offerings> \\
--header 'accept: application/json' \\
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE'Example Response – JSON
{
"data": {
"supplementary": {
"plans": [
{
"id": "Bbc51b6b",
"link": "<http://example.com>",
"name": "Premium",
"providerName": "Safety Wing",
"category": "HEALTH",
"guideUrl": "<http://example.com/guide>",
"estimatedPrice": {
"decimal": "70.0",
"currencyCode": "EUR"
},
"costSplitAllowed": false,
"dependentsConfig": "ALLOWED",
"maxDependents": 0,
"minEmployerContribution": 100,
"supportedEngagementTypes": ["EMPLOYMENT", "CONTRACT"],
"supportedEorTypes": ["DIRECT", "INDIRECT"],
"fee": {
"decimal": "50.0",
"currencyCode": "USD"
}
}
]
},
"packages": [
{
"id": "B9965d2e",
"name": "Essential package",
"fee": null,
"plans": [
{
"id": "D4c07300",
"link": "<http://example.com>",
"name": "Standard",
"providerName": "Safety Wing",
"category": "HEALTH",
"guideUrl": "<http://example.com/guide>",
"estimatedPrice": {
"decimal": "45.0",
"currencyCode": "EUR"
}
}
]
}
]
}
}Step 2: Provision Benefits
Once you've selected your benefits, use the Provision benefits endpoint to set them up.
Important: This is a PUT request — every submission replaces the current provisioning.
Pre-conditions
- Engagement must not be engaged yet.
Contribution Value Logic:
team_member:0.5= 50% contribution;1.0= 100% coverage.max_dependents_covered: Enforces the cost split logic described in Section 3C.
Endpoint
PUT /v0.1/benefits/engagements/{engagement_id}
Description
Provisions benefits for an engagement before the team member becomes engaged (onboarded). This endpoint sets up the initial benefits configuration including package selection and employer contribution percentages. Each PUT request replaces the entire benefits configuration.
Path Parameters
engagement_id(required, string): The unique identifier of the engagement
Request Body Fields
package(optional, object): The benefit package to provisionid(required, string): Package ID from offerings endpointplans[](optional, array): Override employer contributions for specific plansid(required, string): Plan IDemployer_contributions(optional, object):team_member(decimal): Employer contribution for team member (0.0-1.0)dependents(decimal): Employer contribution for dependents (0.0-1.0)max_dependents_covered(integer): Maximum number of dependents covered by employer
supplementary(optional, object): Supplementary plans to provisionplans[](array): Array of supplementary plans- Same structure as package.plans
Response Fields
data.assigned_packages(object): The provisioned package and its enrollmentstier(object): Package informationid: Package unique identifiername: Package display name
enrollments[]: Array of enrollments created for the packageid: Enrollment unique identifierplan: Full plan details (same structure as offerings)assigned_packages: Reference to the parent packageid: Package IDname: Package name
state: Enrollment status (PROVISIONED, APPLIED, ENROLLED, etc.)employerContribution: Applied contribution ratesteamMember: Decimal value (0.0-1.0)dependents: Decimal value (0.0-1.0)maxDependentsCovered: Integer
data.supplementary.enrollments[]: Array of supplementary enrollments (same structure as package enrollments)
Payload Schema
{
"supplementary": {
"plans": [
{
"id": "PLAN_ID",
"employer_contributions": {
"team_member": 0.5,
"max_dependents_covered": 4,
"dependents": 0.25
}
}
]
},
"package": {
"id": "PACKAGE_ID",
"plans": [
{
"id": "PLAN_ID",
"employer_contributions": {
"team_member": 0.55,
"max_dependents_covered": 6,
"dependents": 0.35
}
}
]
}
}Example Request – Bash
curl --request PUT \\
--url <https://api.oysterhr.com/v0.1/benefits/engagements/ENGAGEMENT_ID> \\
--header 'accept: application/json' \\
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE' \\
--header 'content-type: application/json' \\
--data '{
"package": {
"id": "B9965d2e"
}
}'Example Response – JSON
{
"data": {
"assigned_packages": {
"tier": {
"id": "B9965d2e",
"name": "Competitive package"
},
"enrollments": [
{
"id": "B552cefb",
"plan": {
"id": "D4c07300",
"link": "<http://example.com>",
"name": "Standard",
"providerName": "Safety Wing",
"category": "HEALTH",
"guideUrl": "<http://example.com/guide>",
"estimatedPrice": {
"decimal": "50.0",
"currencyCode": "USD"
},
"costSplitAllowed": false,
"dependentsConfig": "ALLOWED",
"maxDependents": 0,
"minEmployerContribution": 100,
"supportedEngagementTypes": ["EMPLOYMENT", "CONTRACT"],
"supportedEorTypes": ["DIRECT", "INDIRECT"],
"fee": {
"decimal": "50.0",
"currencyCode": "USD"
}
},
"assigned_packages": {
"id": "B9965d2e",
"name": "Competitive package"
},
"state": "PROVISIONED",
"employerContribution": {
"dependents": 0.35,
"maxDependentsCovered": 6,
"teamMember": 0.55
}
}
]
},
"supplementary": {
"enrollments": []
}
}
}Step 3: View Engagement Benefits
To view all benefits (packages and supplementary enrollments) for an engagement, use the Retrieve an engagement's benefits endpoint.
When to use
- To confirm successful provisioning
- To view current benefit structure
- To check employer contributions
- To see all assigned packages and supplementary enrollments
Endpoint
GET /v0.1/benefits/engagements/{engagement_id}/
Description
Retrieves all benefits (packages and supplementary enrollments) for a specific engagement. This endpoint provides a complete view of the benefits structure including assigned packages with their enrollments and any supplementary enrollments. Use this after provisioning to confirm the setup or to view the current state of benefits.
Path Parameters
engagement_id(required, string): The unique identifier of the engagement
Response Fields
data.assignedPackages[]: Array of assigned benefit packages (typically one, as only one package can be assigned)assigned_package(object): The assigned package detailsid: Package unique identifiername: Package display namefee: Package-level fee (null if no fee)decimal: Fee amount as stringcurrencyCode: ISO 4217 currency code
plans[]: Array of plans included in this packageid: Plan unique identifiername: Plan display nameproviderName: Insurance provider namecategory: Plan category (HEALTH, PENSION, etc.)link: External provider linkguideUrl: Plan documentation URLestimatedPrice: Estimated monthly costdecimal: Amount as stringcurrencyCode: ISO 4217 currency code
enrollments[]: Array of enrollments for this packageid: Enrollment unique identifierplan: Detailed plan information (full plan object)assigned_packages: Reference back to the packageid: Package IDname: Package name
state: Current enrollment status (PROVISIONED, APPLIED, ENROLLED, CANCELED, FINISHED, OPTED_OUT)employerContribution: Employer contribution configurationteamMember: Contribution percentage for team member (decimal)dependents: Contribution percentage for dependents (decimal)maxDependentsCovered: Maximum dependents covered by employer (integer)
data.supplementary.enrollments[]: Array of supplementary enrollments not part of a package- Same structure as package enrollments
- Note:
assigned_packageswill be null for supplementary enrollments
Example Request
curl --request GET \\
--url <https://api.oysterhr.com/v0.1/benefits/engagements/ENGAGEMENT_ID/> \\
--header 'accept: application/json' \\
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE'Example Response – JSON
{
"data": {
"assigned_packages": [
{
"package": {
"id": "B9965d2e",
"name": "Essential package",
"fee": null,
"plans": [
{
"id": "D4c07300",
"link": "<http://example.com>",
"name": "Standard",
"providerName": "Safety Wing",
"category": "HEALTH",
"guideUrl": "<http://example.com/guide>",
"estimatedPrice": {
"decimal": "45.0",
"currencyCode": "EUR"
}
}
]
},
"enrollments": [
{
"id": "Bdae6827",
"plan": {
"id": "Je54b1c4",
"link": "<https://benefits-plan-external-link-1.example.com>",
"name": "standard",
"providerName": "Borer-Schaden",
"category": "HEALTH",
"guideUrl": "<https://benefits-plan-guide-url-1.example.com>"
},
"assigned_packages": {
"id": "B9965d2e",
"name": "Essential package"
},
"state": "ENROLLED",
"employerContribution": {
"dependents": 0.35,
"maxDependentsCovered": 6,
"teamMember": 0.55
}
}
]
}
],
"supplementary": {
"enrollments": []
}
}
}Step 4: View Individual Enrollment Details
To see details of a specific enrollment, use the Retrieve a benefit enrollment endpoint.
Note on Data Formats: While requests may use decimals (e.g., 0.5), the response below may return normalized values (e.g., 100 for 100% or full coverage).
Endpoint
GET /v0.1/benefits/enrollments/{enrollment_id}
Description
Retrieves detailed information about a specific benefit enrollment. This endpoint provides complete enrollment details including plan information, package association (if applicable), current status, and employer contribution configuration.
Path Parameters
enrollment_id(required, string): The unique identifier of the enrollment
Response Fields
data(object): Enrollment detailsid: Enrollment unique identifierplan(object): Detailed plan informationid: Plan unique identifiername: Plan display nameproviderName: Insurance/benefit provider namecategory: Plan type (HEALTH, PENSION, LIFE, etc.)link: External provider information URLguideUrl: Plan documentation URLcostSplitAllowed: Whether cost can be split between employer and employeedependentsConfig: ALLOWED, REQUIRED, or NOT_ALLOWEDmaxDependents: Maximum number of dependents (0 = unlimited)minEmployerContribution: Minimum required employer contribution percentagesupportedEngagementTypes: Array of supported engagement typessupportedEorTypes: Array of supported EOR types
assigned_packages(object, nullable): Package this enrollment belongs to (null for supplementary enrollments)id: Package unique identifiername: Package display name
state: Current enrollment statusREQUESTED: Employee has requested the benefitAPPLIED: Request submitted to operations teamIN_PROGRESS: Being processed by operationsENROLLED: Successfully enrolled with providerPROVISIONED: Configured but not yet appliedCANCELED: Enrollment canceledFINISHED: Enrollment completed/terminatedOPTED_OUT: Employee opted out of this benefit
employerContribution(object): Employer contribution configurationteamMember: Contribution percentage for team member (can be 0-100 or 0.0-1.0 depending on normalization)dependents: Contribution percentage for dependentsmaxDependentsCovered: Maximum number of dependents covered by employer
Example Request
curl --request GET \\
--url <https://api.oysterhr.com/v0.1/benefits/enrollments/ENROLLMENT_ID> \\
--header 'accept: application/json' \\
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE'Example Response – JSON
{
"data": {
"id": "Bdae6827",
"plan": {
"id": "Je54b1c4",
"link": "<https://benefits-plan-external-link-1.example.com>",
"name": "standard",
"providerName": "Wolff Group",
"category": "HEALTH",
"guideUrl": "<https://benefits-plan-guide-url-1.example.com>",
"costSplitAllowed": false,
"dependentsConfig": "ALLOWED",
"maxDependents": 0,
"minEmployerContribution": 100,
"supportedEngagementTypes": ["EMPLOYMENT", "CONTRACT"],
"supportedEorTypes": ["DIRECT", "INDIRECT"]
},
"assigned_packages": {
"id": "B9965d2e",
"name": "Essential package"
},
"state": "ENROLLED",
"employerContribution": {
"dependents": 100,
"teamMember": 100
}
}
}Step 5: Request Additional Benefits (optional)
You can request optional supplementary (add-on) plans for already engaged team members by using the Request benefits endpoint.
Note: Unlike Provisioning, this is a POST request and adds to existing benefits without replacing the package.
Endpoint
POST /v0.1/benefits/engagements/{engagement_id}/enrollments
Description
Requests additional supplementary benefits for an already engaged team member. Unlike the provisioning endpoint, this adds supplementary plans without affecting the existing package. Use this endpoint to add optional benefits after the team member has been onboarded.
Path Parameters
engagement_id(required, string): The unique identifier of the engagement
Request Body Fields
plan_id(required, string): The unique identifier of the supplementary plan to requestemployer_contribution(optional, object): Employer contribution configurationteam_member(decimal): Employer contribution percentage for team member (0.0-100.0)dependents(decimal): Employer contribution percentage for dependents (0.0-100.0)max_dependents_covered(integer): Maximum number of dependents covered by employer
Response Fields
data(object): Created enrollment detailsid: Enrollment unique identifierplan(object): Requested plan informationid: Plan unique identifiername: Plan display nameproviderName: Provider namecategory: Plan category (HEALTH, PENSION, etc.)
state: Initial enrollment status (typically REQUESTED)employerContribution(object): Applied contribution configurationteamMember: Decimal valuedependents: Decimal valuemaxDependentsCovered: Integer
Payload Example
{
"plan_id": "ANYSTRONGID",
"employer_contribution": {
"team_member": 55.5,
"dependents": 25.2,
"max_dependents_covered": 4
}
}Example Response – JSON
{
"data": {
"id": "F08d621d",
"plan": {
"id": "Bbc51b6b",
"name": "Pension",
"providerName": "Test nationale nederlanden",
"category": "PENSION"
},
"state": "REQUESTED",
"employerContribution": {
"dependents": 0.35,
"maxDependentsCovered": 6,
"teamMember": 0.55
}
}
}API Endpoints Summary
| Endpoint | Method | Purpose | Availability |
|---|---|---|---|
/v0.1/benefits/engagements/{id}/offerings | GET | List available benefit packages and plans for an engagement | v0.1 & v1 |
/v0.1/benefits/engagements/{id} | PUT | Provision benefits for an engagement | v0.1 & v1 |
/v0.1/benefits/engagements/{id}/ | GET | View all benefits (packages + enrollments) for an engagement | v0.1 only |
/v0.1/benefits/enrollments/{id} | GET | View details of a specific enrollment | v0.1 & v1 |
/v0.1/benefits/engagements/{id}/enrollments | POST | Request additional supplementary benefits | v0.1 & v1 |
Troubleshooting & FAQ
Q: I sent a Provisioning request, but the status is not 'ENROLLED'. Why?
A: PROVISIONED is the correct status for a new setup. It will only move to APPLIED and then ENROLLED after the Team Member completes their onboarding and becomes "engaged" in the Oyster system.
Q: What happens if an employee has 5 dependents, but I set max_dependents_covered to 4?
A: The API will not throw an error. The system simply applies the "Cost Split Rule": the employer contribution applies to the first 4. The 5th dependent is enrolled, but the employee pays 100% of that specific premium.
Q: Can I select a Package and a Supplementary plan?
A: Yes. You can select one Package and multiple Supplementary plans. However, you cannot select two Packages.
Q: How do I view all enrollments for an engagement?
A: Use the GET /v0.1/benefits/engagements/{engagement_id}/ endpoint which returns all packages and their enrollments, plus supplementary enrollments in a single response.
Q: What's the difference between the provisioning response and the benefits response?
A: The provisioning endpoint (PUT) returns the structure with assigned_packages.tier and assigned_packages.enrollments. The benefits endpoint (GET) returns packages array where each item has assigned_packages (tier info) and enrollments. Both provide the same enrollment data but structured differently for their use cases.
Q: What does the assigned_packages field represent?
A: The assigned_packages field contains information about the benefit package that has been assigned to the enrollment. For package enrollments, this will contain the package ID and name. For supplementary enrollments that are not part of a package, this field will be null.
Q: What OAuth scopes do I need?
A: Read operations (GET endpoints) require the read scope. Write operations (PUT, POST) require the manage scope. Make sure your OAuth token has the appropriate scopes for the operations you want to perform.
Q: What are all the possible enrollment states?
A: Enrollments can have the following states:
REQUESTED: Employee or API has requested the benefitPROVISIONED: Benefits configured via API before engagementAPPLIED: Request submitted to operations team for processingIN_PROGRESS: Being processed by operationsENROLLED: Successfully enrolled with the insurance providerCANCELED: Enrollment was canceledFINISHED: Enrollment completed or terminatedOPTED_OUT: Employee chose not to enroll in this benefit
Updated about 3 hours ago