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:
| Key | Type | Description | Notes |
|---|---|---|---|
const | String | Stored value submitted in the API request | For example, "const": "Basic Business Travel (Expatriate Group - Basic Business Travel)" |
title | String | Display label for the tier | For example, "title": "Basic Business Travel - 11.45 USD/mo" |
x-jsf-presentation.description | String | Description of the tier | May include HTML |
x-jsf-presentation.meta | JSON object | Additional metadata | May 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
optionValuesthat map to tierconstvalues
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,
benefitsfield 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 andx-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.
- Make the request to Show form schema by providing the specific
country_code(ISO 3-digit alphabetic code) andform=contract_details. - In the returned schema, find the
benefitsproperty. - This property indicates
- Whether benefits are single-question (if
type: "string") or multi-question (iftype: "object") - Which tiers are currently allowed (
oneOf) - UI metadata under
x-jsf-presentation, including grouping information
- Whether benefits are single-question (if
Step 2 — Generate Benefits UI (Optional)
Use the retrieved schema to generate the benefits selection interface. For example:
| Schema Value | UI Implication |
|---|---|
type: "string" | render a single list of options |
type: "object" | render grouped benefit sections |
inputType: "radio" | single tier selection |
meta.subGroups | organize 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-formv0.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_detailsschema for the employee's country. - Submit a new
contract_details.benefitspayload 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"]}
"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
benefitsfield incontract_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.benefitsusing values from the latest schema. - If your integration does not manage benefits through this endpoint, omit
contract_details.benefitsentirely from the payload. Use theskip_benefitsflag 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
oneOfoptions, prompt the user to reselect a benefit tier and submit an updated employment request.
Updated about 11 hours ago