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:

FeatureBenefit PackagesSupplementary Benefits
DefinitionComprehensive bundles (e.g., "Essential", "Competitive").Standalone, discretionary plans (e.g., Pet Insurance).
CompositionTypically combines statutory (mandatory) items with core health/insurance.Single-focus plans.
RulesRequired in some regions (e.g., Spain). You can select only one package.Optional. You can select multiple supplementary plans.
VisibilityA specific benefit plan cannot exist simultaneously as a package component and a supplementary option.
FeeA 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:

  1. 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.
  2. APPLIED: The TM has become engaged. The system has locked the configuration and sent the request to our internal operations team.
  3. 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 packages
    • id: Package unique identifier (used for provisioning)
    • name: Package display name
    • fee: Package-level fee (covers all plans in the package)
      • decimal: Fee amount as string
      • currencyCode: ISO 4217 currency code
    • plans[]: Array of plans included in this package
      • id: Plan unique identifier
      • name: Plan display name
      • providerName: Insurance/benefit provider name
      • category: Plan type (HEALTH, PENSION, LIFE, etc.)
      • link: External provider information URL
      • guideUrl: Plan guide/documentation URL
      • estimatedPrice: Estimated monthly cost per person
        • decimal: Amount as string
        • currencyCode: 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 employee
    • dependentsConfig: ALLOWED, REQUIRED, or NOT_ALLOWED
    • maxDependents: Maximum number of dependents allowed (0 = unlimited)
    • minEmployerContribution: Minimum employer contribution percentage
    • supportedEngagementTypes: 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 provision
    • id (required, string): Package ID from offerings endpoint
    • plans[] (optional, array): Override employer contributions for specific plans
      • id (required, string): Plan ID
      • employer_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 provision
    • plans[] (array): Array of supplementary plans
      • Same structure as package.plans

Response Fields

  • data.assigned_packages (object): The provisioned package and its enrollments
    • tier (object): Package information
      • id: Package unique identifier
      • name: Package display name
    • enrollments[]: Array of enrollments created for the package
      • id: Enrollment unique identifier
      • plan: Full plan details (same structure as offerings)
      • assigned_packages: Reference to the parent package
        • id: Package ID
        • name: Package name
      • state: Enrollment status (PROVISIONED, APPLIED, ENROLLED, etc.)
      • employerContribution: Applied contribution rates
        • teamMember: 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 details
      • id: Package unique identifier
      • name: Package display name
      • fee: Package-level fee (null if no fee)
        • decimal: Fee amount as string
        • currencyCode: ISO 4217 currency code
      • plans[]: Array of plans included in this package
        • id: Plan unique identifier
        • name: Plan display name
        • providerName: Insurance provider name
        • category: Plan category (HEALTH, PENSION, etc.)
        • link: External provider link
        • guideUrl: Plan documentation URL
        • estimatedPrice: Estimated monthly cost
          • decimal: Amount as string
          • currencyCode: ISO 4217 currency code
    • enrollments[]: Array of enrollments for this package
      • id: Enrollment unique identifier
      • plan: Detailed plan information (full plan object)
      • assigned_packages: Reference back to the package
        • id: Package ID
        • name: Package name
      • state: Current enrollment status (PROVISIONED, APPLIED, ENROLLED, CANCELED, FINISHED, OPTED_OUT)
      • employerContribution: Employer contribution configuration
        • teamMember: 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_packages will 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 details
    • id: Enrollment unique identifier
    • plan (object): Detailed plan information
      • id: Plan unique identifier
      • name: Plan display name
      • providerName: Insurance/benefit provider name
      • category: Plan type (HEALTH, PENSION, LIFE, etc.)
      • link: External provider information URL
      • guideUrl: Plan documentation URL
      • costSplitAllowed: Whether cost can be split between employer and employee
      • dependentsConfig: ALLOWED, REQUIRED, or NOT_ALLOWED
      • maxDependents: Maximum number of dependents (0 = unlimited)
      • minEmployerContribution: Minimum required employer contribution percentage
      • supportedEngagementTypes: Array of supported engagement types
      • supportedEorTypes: Array of supported EOR types
    • assigned_packages (object, nullable): Package this enrollment belongs to (null for supplementary enrollments)
      • id: Package unique identifier
      • name: Package display name
    • state: Current enrollment status
      • REQUESTED: Employee has requested the benefit
      • APPLIED: Request submitted to operations team
      • IN_PROGRESS: Being processed by operations
      • ENROLLED: Successfully enrolled with provider
      • PROVISIONED: Configured but not yet applied
      • CANCELED: Enrollment canceled
      • FINISHED: Enrollment completed/terminated
      • OPTED_OUT: Employee opted out of this benefit
    • employerContribution (object): Employer contribution configuration
      • teamMember: Contribution percentage for team member (can be 0-100 or 0.0-1.0 depending on normalization)
      • dependents: Contribution percentage for dependents
      • maxDependentsCovered: 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 request
  • employer_contribution (optional, object): Employer contribution configuration
    • team_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 details
    • id: Enrollment unique identifier
    • plan (object): Requested plan information
      • id: Plan unique identifier
      • name: Plan display name
      • providerName: Provider name
      • category: Plan category (HEALTH, PENSION, etc.)
    • state: Initial enrollment status (typically REQUESTED)
    • employerContribution (object): Applied contribution configuration
      • teamMember: Decimal value
      • dependents: Decimal value
      • maxDependentsCovered: 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

EndpointMethodPurposeAvailability
/v0.1/benefits/engagements/{id}/offeringsGETList available benefit packages and plans for an engagementv0.1 & v1
/v0.1/benefits/engagements/{id}PUTProvision benefits for an engagementv0.1 & v1
/v0.1/benefits/engagements/{id}/GETView all benefits (packages + enrollments) for an engagementv0.1 only
/v0.1/benefits/enrollments/{id}GETView details of a specific enrollmentv0.1 & v1
/v0.1/benefits/engagements/{id}/enrollmentsPOSTRequest additional supplementary benefitsv0.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 benefit
  • PROVISIONED: Benefits configured via API before engagement
  • APPLIED: Request submitted to operations team for processing
  • IN_PROGRESS: Being processed by operations
  • ENROLLED: Successfully enrolled with the insurance provider
  • CANCELED: Enrollment was canceled
  • FINISHED: Enrollment completed or terminated
  • OPTED_OUT: Employee chose not to enroll in this benefit