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
- Replace
{BASE}withhttps://customer-api.baloolearning.com/v1(or your provided host). - Send
Authorization: Bearer {your-api-credential}on every call. - 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:
| Field | Meaning |
|---|---|
maxPaths | Cap on how many selected paths the user may keep. Omit for unlimited. |
leafRequired | When true, each path must close on a leaf (no children). In Configure, required attributes write this as true; optional attributes as false. |
presentation | SINGLE (one searchable control) or STEPPED (a new input per level). |
allowsTopLevelFreeText | When 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:
- Create the field with root choices only.
PUT {BASE}/custom-fields/{customFieldId}with the full list: keep existingchoiceIds, add children withparentChoiceIdset to those ids (omitchoiceIdonly 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:
| Value | Meaning |
|---|---|
IS_ANY | User has any value on the field |
IS_ALL | User has every choice (MULTI_SELECT only) |
IS_UNDER | User 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.