Understanding rulesets

How to use rulesets with the Oyster API

Some of our endpoints - particular those for creating resources - have more complicated schemas, rules and source data; and some of those rules are dependent on external factors such as:

  • The team member's home country
  • The currency

Representing these rules in static documentation would be impractical, so instead this information is available via rulesets.

What is a ruleset?

A ruleset is a JSON document that describes the schema and rules for a particular endpoint. Some endpoints have multiple rulesets - which depend on external factors such as country code - and some endpoints have a single default ruleset that applies to all cases.

Structure of a ruleset

A ruleset follows the following JSON Schema format:

{
  "type": "object",
  "properties": {
    "rulesetId": {
      "type": "string",
      "title": "The unique ID of this ruleset",
      "description": "The ruleset ID is unique within the domain"
    },
    "domain": {
      "type": "string"
    },
    "scope": {
      "type": "object",
      "title": "The scope (e.g. country) for which this ruleset applies",
      "description": "For default rulesets, the scope will be empty",
      "additionalProperties": {
        "type": "string"
      }
    },
    "context": {
      "type": "object",
      "title": "Useful information related to the scope of the ruleset",
      "additionalProperties": {
        "type": "string"
      }
    },
    "schemas": {
      "type": "object",
      "properties": {
        "post": {
          "oneOf": [
            {
              "type": "object",
              "title": "Schema for body structure of POST requests",
              "description": "Must be a schema conforming to JSON Schema"
            },
            {
              "enum": ["patch", "put"],
              "title": "Reuse the schema defined for PATCH or PUT requests"
            }
          ]
        },
        "patch": {
          "oneOf": [
            {
              "type": "object",
              "title": "Schema for body structure of PATCH requests",
              "description": "Must be a schema conforming to JSON Schema"
            },
            {
              "enum": ["post", "put"],
              "title": "Reuse the schema defined for POST or PUT requests"
            }
          ]
        },
        "put": {
          "oneOf": [
            {
              "type": "object",
              "title": "Schema for body structure of PUT requests",
              "description": "Must be a schema conforming to JSON Schema"
            },
            {
              "enum": ["post", "patch"],
              "title": "Reuse the schema defined for POST or PATCH requests"
            }
          ]
        }
      }
    },
    "sources": {
      "type": "object",
      "title": "Provides information about how to fetch source data",
      "description": "Source data are things like lists of values for dropdown selections"
    }
  },
  "required": ["rulesetId", "domain", "scope", "schemas"]
}

rulesetId

The unique ID of this ruleset within its domain. Default rulesets will have a ruleset ID of DEFAULT. The rulesetId can be used to fetch a specific ruleset; e.g.:

/{version}/expenses/rulesets/default

/{version}/bank_accounts/rulesets/KES-KE-LOCAL

domain

If the ruleset does not specify a domain, then the domain can be assumed to be the part of the path preceding /rulesets; i.e.:

The domain of a ruleset returned from /{version}/expenses/rulesets is expenses.

scope

Dynamic rulesets are those that are dependent on external factors such as country. Those external factors define the scope of the ruleset. For example, the ruleset for hiring team members in the United States would be fetched using:

/{version}/hiring/rulesets?country_code=US

In this case, the scope of the returned ruleset would be:

{
  "scope": {
    "countryCode": "US"
  }
}

context

When fetching a ruleset, there might be additional useful information provided. For example, in the payroll_changes domain, you need to provide an engagement ID scope to fetch the correct ruleset for a given engagement:

/{version}/payroll_changes/rulesets?engagement_id=ABCD1234

This will return a ruleset with the following scope:

{
  "scope": {
    "engagementId": "ABCD1234"
  }
}

As part of your use case, you may also need to know the next cut-off date of that engagement's payroll, and also the engagement's salary currency. These will be provided by the context property; e.g.:

{
  "context": {
    "currencyCode": "USD",
    "cutoffDate": "2024-09-10"
  }
}

The context is not part of the ruleset itself, but is instead relevant to the scope of the ruleset and the time it was requested.

schemas

For most POST endpoints, you will be required to include a payload of data. That data must be formatted correctly according to the defined schema for that endpoint, otherwise you may receive a 400 - Bad Request response from the endpoint. Endpoints with dynamic rulesets may also have dynamic schemas; and so the schema for a specific ruleset is provided in the schemas property. Schemas are JSON Schema documents.

sources

You may be required to provide values to POST endpoints that come from predefined lists. Sometimes these values are provided as enum fields in the schema. However, enums are not always the right mechanism to provide lists of data; in particular the do not support descriptions or other meta information. This is where sources come in.

The sources section of the schema will provide you with the means to fetch source data for the given ruleset. For example, when posting a new expense to the /expenses domain, you need will to include a value for the category property, and the value must come from a list of supported expense categories.

In the expense ruleset you will see the categories source:

{
  "sources": {
    "categories": {
      "params": {}
    }
  }

This means you can fetch the expenses categories from the following endpoint:

/{version}/expenses/rulesets/{rulesetId}/categories

params

Some sources endpoints may require additional parameters, and these are described by the params property. If params is empty, then you can call the source endpoint with no additional parameters.

Default rulesets

Some endpoints have a single default ruleset that applies to all cases. Default rulesets have a rulesetId of DEFAULT.

If a /rulesets endpoint returns a single ruleset with the ruleset ID DEFAULT, then that is the only ruleset the endpoint requires.

The default ruleset can always be fetched using the /rulesets/default endpoint; e.g.:

/{version}/expenses/rulesets/default

Endpoints that do not support a default ruleset will return 404 - Not Found.

The default ruleset can also be fetched using the convenience /ruleset endpoint; e.g.:

/{version}/expenses/ruleset

Endpoints that do not support a default ruleset will return 404 - Not Found.

Default sources

For endpoints with a default ruleset and sources, additional convenience endpoints exist to support sources. For example, for expense categories:

/{version}/expenses/categories

...is the equivalent of:

/{version}/expenses/rulesets/default/categories

Endpoints that do not support a default ruleset will return 404 - Not Found.