SkillhabitDocs

Hierarchical Custom Fields

Create nested attribute choices, assign path answers on users, and browse choice children over the External API.

What This Is For

Skillhabit custom fields (attributes) can be flat lists or trees. Parent choices point at children with parentChoiceId. Integrations create the tree, set user answers as paths, and optionally page through children when building pickers.

Interactive schemas: Custom Fields and Users in the OpenAPI reference. Product setup: Custom Attribute Fields.

Who This Is For

Integration developers with an API key. Workspace admins still own what the fields mean in Configure.

Before You Start

  1. Replace {BASE} with https://customer-api.baloolearning.com/v1 (or your provided host).
  2. Send Authorization: Bearer {your-api-credential} on every call.
  3. Prefer CHOICE (single path) or MULTI_SELECT (several paths). TEXT fields stay flat strings.

Create a Hierarchical Field

POST {BASE}/custom-fields

Omit choiceId on create—Skillhabit generates ids. Nest with parentChoiceId. Field-level hierarchy settings sit beside the choices:

FieldMeaning
maxPathsCap on how many selected paths the user may keep. Omit for unlimited.
leafRequiredWhen true, each path must close on a leaf (no children). In Configure, required attributes write this as true; optional attributes as false.
presentationSINGLE (one searchable control) or STEPPED (a new input per level).
allowsTopLevelFreeTextWhen true, root picks may include freeText. Prefer per-choice allowsFreeText (the Allow Other option in Configure) for level-scoped free text.

Per choice you can also set allowsFreeText, stepLabel, and an optional branch maxPaths. Free-text answers are not usable for smart-group style filtering.

POST {BASE}/custom-fields
Authorization: Bearer {your-api-credential}
Content-Type: application/json
{
  "type": "MULTI_SELECT",
  "name": "Affiliation",
  "description": "Region and site",
  "isOptional": true,
  "maxPaths": 3,
  "leafRequired": true,
  "presentation": "STEPPED",
  "allowsTopLevelFreeText": false,
  "choices": [
    { "name": "Nordics" },
    { "name": "Stockholm", "parentChoiceId": "{nordicsChoiceId}", "stepLabel": "Which site?" },
    { "name": "Other", "parentChoiceId": "{nordicsChoiceId}", "allowsFreeText": true }
  ]
}

On the first create you will not know child parentChoiceId values yet. Typical pattern:

  1. Create the field with root choices only.
  2. PUT {BASE}/custom-fields/{customFieldId} with the full list: keep existing choiceIds, add children with parentChoiceId set to those ids (omit choiceId only for brand-new choices).

Or create roots and children in one body after you invent stable ids yourself and send them on create (the API accepts client-supplied ids when present).

List Children (Paged)

GET {BASE}/custom-fields/{customFieldId}/choices?parentChoiceId={id}&page=0&size=50

Omit parentChoiceId for roots. Optional q searches by name (case-insensitive).

Use this when your UI loads one level at a time instead of the full tree from GET /custom-fields.

Set a User’s Paths

POST {BASE}/users/{userId}/update-custom-field-choice

For hierarchical fields, send paths. Do not mix paths with flat choiceId, choiceIds, or answer on the same request.

Each path closes on the deepest choiceId you want to store. Add freeText only when that choice (or top-level free-text policy) allows it.

POST {BASE}/users/{userId}/update-custom-field-choice
Authorization: Bearer {your-api-credential}
Content-Type: application/json
{
  "customFieldId": "{affiliationFieldId}",
  "choiceId": null,
  "answer": null,
  "choiceIds": null,
  "paths": [
    { "choiceId": "{stockholmChoiceId}" },
    { "choiceId": "{otherChoiceId}", "freeText": "Malmö office" }
  ]
}

Flat fields still use choiceId (CHOICE), choiceIds (MULTI_SELECT), or answer (TEXT) as before.

Smart Groups and Descendants

When creating or updating a smart group over the API, conditions can use systemCondition:

ValueMeaning
IS_ANYUser has any value on the field
IS_ALLUser has every choice (MULTI_SELECT only)
IS_UNDERUser path closes on the given choice or any live descendant—pass the ancestor id in choiceIds

IS_UNDER is for hierarchical trees. Exact choice lists still work with choiceIds alone when you want only those ids.

Result

  • A nested attribute definition your people see under Configure → Attributes.
  • User answers stored as closed paths (and optional free text).
  • Smart groups that can match a whole branch with IS_UNDER.