Config API
Base path: /api/config
All endpoints use POST. Authentication required unless otherwise noted.
For the overall architecture, see Configuration System Architecture.
Current consumers of catalog keys
| Keys | Consumer | Resolved scope |
|---|---|---|
auth.registration.enabled | AuthController.Register() — returns 503 when "false" | global |
auth.captcha.enabled | AuthController.Register() — requires captchaId/captchaProof when "true" | global |
auth.password.min_length, auth.password.require_uppercase, auth.password.require_digit, auth.password.require_special | IPasswordGenerator — used by POST /api/cloud/tenant/create and POST /api/auth/register | global-only |
forms.{dataArea}.field_config | ICustomFieldValidationService + frontend useDataAreaConfig — per-field overrides (hidden, required, label) for each DataArea | tenant |
forms.{dataArea}.custom_fields | ICustomFieldValidationService + frontend useDataAreaConfig — array of tenant-defined additional fields; values stored in colcustom JSONB | tenant |
permissions.section_visibility | Frontend PermissionGate — map {section: number[]} of allowed userIds per section | tenant, group |
ui.theme, ui.density, ui.sidebar_collapsed, ui.dashboard_layout | Frontend useConfig / useAppConfig | tenant, group, user |
branding.app_name, branding.primary_color | Frontend useConfig | tenant, group |
system.group_conflict_strategy | ConfigMergeService — strategy for resolving conflicts between group overrides | global, tenant |
The IConfigMergeService.ResolveGlobalAsync method is used when no tenant/user context exists yet (e.g. tenant creation, registration).
auth.registration.enabled
| Property | Value |
|---|---|
| Category | auth |
| Value type | bool |
| Default | "true" |
| Allowed levels | 1 (global only) |
Controls whether the self-registration endpoint (POST /api/auth/register) is open to new users. Set to "false" to close registration without disabling existing accounts.
auth.captcha.enabled
| Property | Value |
|---|---|
| Category | auth |
| Value type | bool |
| Default | "false" |
| Allowed levels | 1 (global only) |
When "true", POST /api/auth/register requires the caller to first obtain a POW challenge from POST /api/auth/captcha/challenge, solve it client-side, and submit captchaId + captchaProof with the registration request. See Authentication — POW CAPTCHA for the full flow.
ICustomFieldValidationService
HrStudio2.Api.Services.Forms.CustomFieldValidationService is registered as a scoped service in Program.cs.
Expected usage: called by form endpoints (e.g. POST /api/hr/employee/create) to validate values in the colcustom payload before saving.
var errors = await customFieldValidationService.ValidateAsync(
dataAreaName: "employee_registry",
tenantId: currentTenantId,
values: request.Custom,
ct);
if (errors.Count > 0)
return UnprocessableEntity(new { errors });
Reads forms.{dataAreaName}.custom_fields from the tenant-level resolved config (ResolveForTenantAsync) and validates type, required, and select options.
Catalog management
SuperAdmin only.
POST /api/config/catalog/list
Returns all catalog entries, optionally filtered by category.
Request:
{ "category": "auth" }
Response:
[
{
"keyId": 1,
"code": "auth.password.min_length",
"category": "auth",
"label": "Minimum password length",
"valueType": "int",
"defaultValue": "8",
"allowedValues": "{\"min\":6,\"max\":128}",
"allowedLevels": 3,
"isSensitive": false,
"isActive": true
}
]
POST /api/config/catalog/get
Returns a single catalog entry by code.
Request:
{ "code": "auth.password.min_length" }
POST /api/config/catalog/create
Creates a new catalog key.
Request:
{
"code": "features.module.training",
"category": "features",
"label": "Training module",
"valueType": "bool",
"defaultValue": "false",
"allowedLevels": 3
}
POST /api/config/catalog/update
Updates metadata of an existing catalog key. Does not change override values.
Request:
{
"code": "features.module.training",
"label": "Training & Development module",
"isActive": true
}
POST /api/config/catalog/delete
Permanently deletes a catalog key and all its overrides (cascade).
Request:
{ "code": "test.demo_key" }
Error 404 if the key does not exist.
Global overrides
SuperAdmin only.
POST /api/config/global/list
Returns all global override rows.
POST /api/config/global/set
Creates or updates a global override (upsert).
Request:
{ "code": "auth.registration.enabled", "value": "false" }
POST /api/config/global/unset
Removes a global override, reverting the key to the catalog default.
Request:
{ "code": "auth.registration.enabled" }
Tenant overrides
SuperAdmin (any tenant) or TenantAdmin (own tenant only).
POST /api/config/tenant/list
Returns all tenant overrides for the caller's tenant (or the specified tenant for SuperAdmin).
Request (SuperAdmin):
{ "tenantId": 42 }
POST /api/config/tenant/set
Upserts a tenant override. Validates that the value does not violate global policy constraints.
Request:
{ "code": "branding.app_name", "value": "My Company HR" }
Error 422 if the key has cck_allowed_levels & 2 = 0 (tenant level not permitted) or if the value violates a global policy constraint.
POST /api/config/tenant/unset
Removes a tenant override, reverting to the global/catalog value.
Request:
{ "code": "branding.app_name" }
Tenant GUID variants (SuperAdmin only)
The three endpoints above identify the tenant by its internal integer ID (ten_internal_id). When calling from an external system that only knows the tenant public UUID (ten_uuid), use the GUID variants instead. They behave identically but resolve the tenant by UUID first.
POST /api/config/tenantguid/list
Lists all overrides for the tenant identified by its public UUID.
Auth: SuperAdmin only.
Request:
{ "tenantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }
Response: same shape as POST /api/config/tenant/list.
POST /api/config/tenant/guid/set
Upserts a tenant override, identifying the tenant by public UUID.
Auth: SuperAdmin only.
Request:
{
"tenantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"code": "branding.app_name",
"value": "My Company HR"
}
Error 422 — same policy checks as POST /api/config/tenant/set.
POST /api/config/tenant/guid/unset
Removes a tenant override, identifying the tenant by public UUID.
Auth: SuperAdmin only.
Request:
{
"tenantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"code": "branding.app_name"
}
Group overrides
TenantAdmin only.
POST /api/config/group/list
Lists group overrides for the caller's tenant, optionally filtered by group type.
Request:
{ "groupType": "mansione" }
Response:
[
{
"code": "ui.density",
"groupType": "mansione",
"groupCode": "DEV",
"value": "compact"
}
]
POST /api/config/group/set
Creates or updates a group override.
Request:
{
"code": "ui.density",
"groupType": "mansione",
"groupCode": "DEV",
"value": "compact"
}
Supported groupType values: mansione, qualifica, sede_op, tipo_tempo, gruppo_tipo, static_list, dynamic_list.
Error 422 if cck_allowed_levels & 4 = 0.
POST /api/config/group/unset
Removes a group override.
Request:
{
"code": "ui.density",
"groupType": "mansione",
"groupCode": "DEV"
}
User overrides
TenantAdmin (any user in their tenant) or authenticated user (own preferences only).
POST /api/config/user/values/list
Lists overrides for a specific user. TenantAdmin may pass any userId; regular users only see their own.
Request:
{ "userId": "uuid-of-user" }
POST /api/config/user/values/set
Creates or updates a user-level override.
Request:
{ "code": "ui.sidebar_collapsed", "value": "true" }
Error 422 if cck_allowed_levels & 8 = 0.
POST /api/config/user/values/unset
Removes a user-level override.
Request:
{ "code": "ui.sidebar_collapsed" }
POST /api/config/user/resolve
Re-fetches and returns the fully resolved config map for the authenticated user. Use this after a TenantAdmin changes settings that should take effect without requiring a re-login.
Request: empty body.
Response:
{
"config": {
"auth.password.min_length": "8",
"ui.theme": "dark",
"features.ai_assistant": "true"
}
}
Error responses
| Status | Meaning |
|---|---|
401 | Not authenticated |
403 | Authenticated but not authorized for this level (e.g. TenantAdmin trying global endpoint) |
404 | Config key code not found in catalog |
422 | Level not permitted for this key, or value violates policy constraint |