Set Employment Benefits

Overview

Employee benefits are configured as part of an employee's contract details. Benefits include health insurance, retirement plans, meal allowances, employee assistance programs, etc.

Each country has its own benefit offerings and compliance requirements. These determine what benefits are available, whether employers can opt out, and how benefits are structured in the API. Because of this variation, benefit options need to always be retrieved dynamically using the schema endpoints.

Prerequisite

Before setting benefits, you must have an existing employment. Learn how to create a new employment by following this guide on Creating Employees with Remote API.

Must Read: Benefit selection is part of employment updates that are blocked once the invitation has been sent to the employee. This means that you must set and finalize benefit selections before sending invite to the employee.

Understanding Benefit Schema Structure

Benefits are defined using a JSON schema returned by the Show form schema endpoint. Your integration should read this schema to determine:

  • how benefits are structured for a specific country
  • which tiers are available
  • what metadata should be displayed in your UI

So, instead of hardcoding benefit options, you can use this schema as the single source of truth.

The schema describes benefit options using three main concepts: groups, tiers, and subgroups.

Group

A group represents a set of related benefit tiers, such as health or retirement.

ℹ️ In multi-question countries, each group typically appears as a property inside the benefits object, for example:

"benefits":
  {
    "properties": {
      "health": { ... },	// group 1
			"retirement": { ... },  // group 2
			"travel": {...},  // group 3
    }
  }

Tier

A tier is a selectable option within a group (corresponds to oneOf within properties). Each tier option includes:

KeyTypeDescriptionNotes
constStringStored value submitted in the API requestFor example, "const": "Basic Business Travel (Expatriate Group - Basic Business Travel)"
titleStringDisplay label for the tierFor example, "title": "Basic Business Travel - 11.45 USD/mo"
x-jsf-presentation.descriptionStringDescription of the tierMay include HTML
x-jsf-presentation.metaJSON objectAdditional metadataMay include benefitGroupId, detailsUrl, displayCost, id, providerName, tierName

Subgroups

Some groups support subgroups to organize tiers in the UI. For example Individual vs Family benefit tiers. These subgroups are defined under x-jsf-presentation.meta.subGroups. Each of these subgroups include:

  • an identifier and label
  • optionValues that map to tier const values

How to Set Benefits on Employment

Benefits are set by updating existing employment. When updating an employment, benefits are passed under contract_details.benefits field, the structure of which depends on the country of employment.

  • In some countries, it is a single string (single-question format)
  • In others, it is an object of strings (multi-question format)

Single-Question Benefits

Some countries require selecting one benefit tier from a single list of benefit options. In this structure, benefits field is typically:

  • type: "string"
  • oneOf: [...] for allowed tiers
  • UI hint: x-jsf-presentation.inputType: "radio"

Example payload:

{
  "contract_details": {
    "benefits": "Basic - Employee only"
  }
}

Multi-Question Benefits

In other countries, you select one tier per group, such as health, retirement, insurance, meal cards, etc. In this structure,

  • benefits field is an object
  • Each key represents a benefit group
  • Each value is the selected benefit trier
  • properties: {...} where each property contains a oneOf
  • UI hint: x-jsf-presentation.inputType: "fieldset" at top level and x-jsf-presentation.inputType: "radio" within each group.

Example payload:

{
  "contract_details": {
    "benefits": {
      "health": "plus_individual_v1",
      "retirement": "standard_pension_v2",
      "life_insurance": "no"
    }
  }
}

Step 1 — Fetch Benefit Schema

Since benefits vary by country and are subject to change, instead of hardcoding benefit tiers, you should always call the Show form schema endpoint to fetch the current benefits schema.

  1. Make the request to Show form schema by providing the specific country_code (ISO 3-digit alphabetic code) and form=contract_details.
  2. In the returned schema, find the benefits property.
  3. This property indicates
    • Whether benefits are single-question (if type: "string") or multi-question (if type: "object")
    • Which tiers are currently allowed (oneOf)
    • UI metadata under x-jsf-presentation, including grouping information

Step 2 — Generate Benefits UI (Optional)

Use the retrieved schema to generate the benefits selection interface. For example:

Schema ValueUI Implication
type: "string"render a single list of options
type: "object"render grouped benefit sections
inputType: "radio"single tier selection
meta.subGroupsorganize tiers into sections

@remoteoss/json-schema-form is a headless form library that takes the returned JSON schema and helps you generate a UI form based on the schema structure and Remote's UI hints in x-jsf-presentation.

Here's a demo implementation that uses React + Formik. Open the example and review the FancyBenefitComponent, including how it is registered and rendered through createHeadlessForm().

⚠️ To follow the example in this section, make sure you're using json-schema-form v0.4.0-beta.0 or later. For more information, please visit json-schema-form usage at Remote.

Step 3 — Collect Benefit Selections

Once you select the desired tiers, collect the values defined by the const fields in the schema.

Step 4 — Submit Employment Update

To submit the selected benefit options, use the Update employment endpoint, pass the employment_id of the employee and add the collected benefit JSON against contract_details in the request body. When submitting, verify that the values are an exact match of values returned by the schema.

An example request looks like:

{
  "contract_details": {
    "benefits": {
      "health": "Plus - Employee Only (Canada Life...)",
      "retirement": "Standard Retirement (Canada Life...)"
    }
  }
}

Remember that benefits must be finalized before sending the employment invitation, since employment updates are blocked after the invite is sent.

Benefit-specific Constraints

Locked Benefits

In certain countries, regulations require that once an employer defined a benefit offer for the first employee, that offer becomes locked for all subsequent employment in that country/jurisdiction.

Once set, you will observe the following in the schema or UI for all later employments:

  • Benefit group containing a default value
  • Only a selectable option may be available, matching the tier chosen for the first employment

Opt-out from Benefits

In some countries, employers are not required to offer certain benefits. In these cases, the schema includes an option indicating that no benefit will be offered.

  • This option appears as "const": "no" in the returned schema
  • In multi-group countries, this option is selectable per benefit group

What you'll see in the schema:

{
  "oneOf": [
    {
      "const": "no",
      "title": "I don't want to offer this benefit.",
    }
  ],
...
}

Troubleshooting

This section covers common issues you may encounter when setting employment benefits using the contract_details.benefits field.

Updates blocked after employment is invited

What happened: Attempts to update contract_details fail after the employee invitation has been sent.

Cause: Employment updates are restricted once the invitation has been sent to the employee.

Solution: Contract amendments cannot be made through the API at this stage. Any changes to the contract post employee invitation can be made through the Remote UI.

"Please reselect benefits" or benefits selection becomes invalid

What happened: An error indicates that benefits must be selected again during the invite or validation process.

Cause: Previously selected benefit tiers may no longer be valid. This can happen if:

  • the schema has changes
  • the selected option is no longer available
  • the default value returned by the API differs from what the integration expects

Solution:

  • Fetch the latest contract_details schema for the employee's country.
  • Submit a new contract_details.benefits payload using latest pulled schema values.
  • Retry the invitation after updating employment.

See section How to set benefits for detailed steps.

422 validation error: "contract_details":{"benefits":["is not accepted"]}

What happened: The API returns a 422 validation error indicating that the submitted benefits value is not accepted.

Cause: The submitted contract_details.benefits value does not match the schema. This may occur when:

  • the payload contains benefit values not present in the current schema
  • the request attempts to bypass benefits handling while still including the benefits field in contract_details
  • the payload structure does not match the expected format (e.g. string vs object)

Solution: Ensure request is consistent with the schema.

  • If you want Remote to validate and store benefits during the employment update, include contract_details.benefits using values from the latest schema.
  • If your integration does not manage benefits through this endpoint, omit contract_details.benefits entirely from the payload. Use the skip_benefits flag when updating employment.
  • Confirm whether the country expects a single-question structure (string) or a multi-question structure (object).

Sandbox pattern: invitation behavior detected when updating employment

What happened: Employment updates trigger invitation-related behavior during testing.

Cause: In some onboarding flows, updating employment data may trigger invitation logic depending on how the update request is executed.

Solution: When testing or updating employment data in multiple steps, use the query parameter ?actions=no_invite. This ensures the request updates employment data without triggering an invitation.

Default benefit value differs from stored selection

What happened: A previously stored benefits selection fails validation or causes issues during the invite flow.

Cause: The default value returned by the API may differ from the integration previously stored. Additionally, the selected tier may no longer exist in the current schema.

Solution: Treat the schema as the single source of truth at runtime.

  • Fetch the schema close to the time the selection needs to be made
  • Store the tier value (const) returned by the schema rather than the display label
  • If a previously stored selection no longer appears in the schema's oneOf options, prompt the user to reselect a benefit tier and submit an updated employment request.


What’s Next

With the benefits selected, you can now invite the employee.