API Reference

SSO Manager API Documentation

Looking for a plainer explanation of what API tokens are and when you'd want one, instead of a full endpoint reference? See API Tokens (in-app) or concepts-api-tokens.md (repo).

Overview

API documentation for the SSO Manager Node application. Provides endpoints for authentication, user management, group management, token management, notifications, and OAuth 2.0 / OpenID Connect.

Base URL: https://your-domain.com

Content Type: application/json (all request and response bodies)


Authentication

Getting a Token

Login via POST /api/auth/login to receive an auth token.

Using the Token

Include the token in every protected request:

auth-token: <token>

Protected Routes

  • /api/user/* — user management
  • /api/group/* — group management
  • /api/token/* — token management
  • /api/notification/* — notifications
  • /api/oauth/client/* — OAuth client management

Permission Groups

Group Grants
app_sso_admin Full user/group/notification management
app_sso_oauth_admin Register and manage OAuth clients
app_sso_invite Invitation management
Group owner Manage membership of that specific group

Self-service: users can always read and modify their own account without admin membership.

Rate Limits

Endpoint Limit
POST /api/auth/login 10 requests / 15 min
POST /api/auth/resetpassword 5 requests / hour
POST /api/auth/otp/request 5 requests / 15 min
POST /api/auth/otp/verify 10 requests / 15 min
POST /api/auth/invite/* 20 requests / hour

Limits are per IP. The server must be behind a trusted proxy for these to apply correctly.


Authentication Endpoints

Base path: /api/auth

Login

POST /api/auth/login — No auth required

Request:

{ "uid": "username", "password": "user_password" }

Response:

{ "login": true, "token": "auth_token_string", "message": "username logged in!" }

Logout

ALL /api/auth/logout — No auth required

Response:

{ "message": "Bye" }

Username Suggestions

Returns available username suggestions based on name and optional date of birth.

GET /api/auth/username-suggestions — No auth required

Query Parameters:

  • givenName — First name
  • sn — Last name
  • dob (optional) — Date of birth (used to generate year-variant suggestions)

Response:

{ "suggestions": ["jsmith", "jsmith1990", "johnsmith"] }

Request Password Reset

POST /api/auth/resetpassword — No auth required

Request:

{ "mail": "user@example.com" }

Response:

{ "message": "If the email address is in our system, you will receive a message." }

The same response is returned whether or not the email exists. Reset tokens expire after 24 hours.


Complete Password Reset

POST /api/auth/resetpassword/:token — No auth required

URL Parameters: token — from the reset email

Request:

{ "password": "new_password", "confirm": "new_password" }

Response:

{ "message": "Password has been changed." }

The token is invalidated after a successful reset.


Request OTP Code

Send a one-time login code via email or SMS.

POST /api/auth/otp/request — No auth required

Request:

{ "login": "username_or_email", "method": "email" }

method must be email or sms. For sms, the account must have a mobile number on file.

Response:

{ "message": "Code sent", "method": "email", "expires_at": 1234567890000 }

Verify OTP Code

Verify a one-time code and receive an auth token. Also marks the corresponding contact method (email or phone) as verified on the account.

POST /api/auth/otp/verify — No auth required

Request:

{ "login": "username_or_email", "code": "123456" }

Response:

{ "login": true, "token": "auth_token_string" }

Returns 401 if the code is invalid or expired.


Accept Invite (with Email Verification)

Create an account from an invite token after verifying email.

POST /api/auth/invite/:token/:mailToken — No auth required

URL Parameters: token — invite token, mailToken — email verification token

Request:

{
  "uid": "username",
  "password": "user_password",
  "givenName": "First Name",
  "sn": "Last Name",
  "mail": "user@example.com"
}

Response:

{ "user": "username", "token": "auth_token_string" }

Send Invite Verification Email

Send an email verification link for an invite token.

POST /api/auth/invite/:token — No auth required

URL Parameters: token — invite token

Request:

{ "mail": "user@example.com" }

Response:

{ "message": "sent" }

Impersonate User (Admin)

Creates a temporary password for a target user and returns it. Allows admins to log in as another user for support purposes.

POST /api/auth/impersonate/:uid — Auth required, app_sso_admin

URL Parameters: uid — target username

Response:

{
  "uid": "target_username",
  "temp_password": "random_temp_password",
  "expires_at": 1234567890000
}

The temp password is short-lived. Any previous impersonation session for the same target is revoked first.


End Impersonation (Admin)

Revoke all active impersonation sessions for a user.

DELETE /api/auth/impersonate/:uid — Auth required, app_sso_admin

URL Parameters: uid — target username

Response:

{ "message": "Impersonation ended for username", "revoked": 1 }

User Management Endpoints

Base path: /api/user

All endpoints require authentication.

List All Users

GET /api/user/app_sso_admin required

Query Parameters:

  • detail (optional) — return full user objects instead of a minimal list

Response:

{
  "results": [
    { "uid": "username", "mail": "user@example.com", "givenName": "First", "sn": "Last" }
  ]
}

Create User

POST /api/user/app_sso_admin required

Request:

{
  "uid": "username",
  "password": "user_password",
  "givenName": "First Name",
  "sn": "Last Name",
  "mail": "user@example.com"
}

Response:

{ "results": { "uid": "username", "dn": "uid=username,ou=people,dc=..." } }

Admin-created accounts have password_must_change set automatically.


Get Current User

GET /api/user/me — Any authenticated user

Returns the full user object for the authenticated user, including onboarding state.

Response:

{
  "uid": "username",
  "mail": "user@example.com",
  "givenName": "First",
  "sn": "Last",
  "dn": "uid=username,ou=people,dc=...",
  "onboardingRequired": "yes",
  "onboardingNeeds": ["tos", "dob", "password"],
  "memberOf": ["cn=group_name,ou=groups,dc=..."]
}

onboardingRequired is "yes" when onboardingNeeds is non-empty. Needs can be "tos", "dob", or "password".


Accept Terms of Service

Mark TOS as accepted for the authenticated user.

POST /api/user/accept-tos — Any authenticated user

Response:

{ "success": true }

Get Admin Stats

Returns user and group counts, recent signups, and inactive users.

GET /api/user/statsapp_sso_admin required

Response:

{
  "totalUsers": 42,
  "activeUsers": 38,
  "inactiveUsers": 4,
  "totalGroups": 10,
  "recentSignups": [
    { "uid": "newuser", "givenName": "New", "sn": "User", "mail": "new@example.com", "createTimestamp": "20240101000000Z" }
  ],
  "inactiveList": [
    { "uid": "lockeduser", "givenName": "Locked", "sn": "User", "mail": "locked@example.com" }
  ]
}

Export Users CSV

Download all users as a CSV file.

GET /api/user/exportapp_sso_admin required

Response: Content-Type: text/csv, attachment download

Columns: uid, givenName, sn, mail, mobile, uidNumber, isActive, createTimestamp


Get User Verification Status

GET /api/user/:uid/verificationapp_sso_admin required

Response:

{
  "uid": "username",
  "emailVerified": true,
  "emailVerifiedAt": 1234567890000,
  "phoneVerified": false,
  "phoneVerifiedAt": null,
  "tosAccepted": true,
  "tosAcceptedAt": 1234567890000
}

Get User

GET /api/user/:uid — Any authenticated user

Response:

{ "results": { "uid": "username", "mail": "user@example.com", "givenName": "First", "sn": "Last" } }

Update User

PUT /api/user/:uid — Own account, or app_sso_admin for others

Request: Any subset of editable fields:

{
  "givenName": "New First Name",
  "sn": "New Last Name",
  "mail": "newemail@example.com",
  "dateOfBirth": "1990-01-15",
  "mobile": "+15551234567",
  "sshPublicKey": "ssh-rsa AAAA..."
}

Response:

{ "results": { <updated user object> }, "message": "Updated username user" }

Set User Active/Inactive

Lock or unlock a user account.

PUT /api/user/:uid/activeapp_sso_admin required

Request:

{ "active": true }

Response:

{ "uid": "username", "active": true, "message": "User username activated" }

Delete User

DELETE /api/user/:uid — Own account, or app_sso_admin for others

Response:

{ "uid": "username", "results": true }

Change Own Password

PUT /api/user/password — Any authenticated user

Request:

{ "password": "new_password", "confirm": "new_password" }

Response:

{ "results": true }

Clears password_must_change on the account.


Change User Password (Admin)

PUT /api/user/:uid/password — Own account, or app_sso_admin for others

Request:

{ "password": "new_password", "confirm": "new_password" }

Response:

{ "results": true, "message": "User username password changed." }

When an admin changes another user's password, password_must_change is set on that account.


Generate Invite Token

Create an invite token, optionally emailing it to the invitee and pre-assigning LDAP groups the new account should be added to on signup.

POST /api/user/inviteapp_sso_admin or app_sso_invite required

Request:

{ "mail": "invitee@example.com", "groups": ["group_name"] }

mail and groups are both optional. If mail is provided, a verification email is sent to that address.

Response:

{
  "token": "invite_token_string",
  "link": "https://your-domain.com/login/invite/invite_token_string",
  "mail_sent": true
}

List Invite Tokens

GET /api/user/inviteapp_sso_admin or app_sso_invite required

Admins (app_sso_admin) see all invite tokens; non-admin app_sso_invite members see only invites they created.

Response:

{
  "results": [
    {
      "token": "invite_token_string",
      "created_by": "username",
      "created_on": 1234567890000,
      "is_valid": true,
      "mail": "invitee@example.com",
      "groups": "[\"group_name\"]"
    }
  ]
}

Update Invite Token

Update the groups an invite will assign, or change/clear the invitee's email (re-sends verification if a new email is set).

PUT /api/user/invite/:tokenapp_sso_admin, or the app_sso_invite member who created the invite

URL Parameters: token — invite token

Request: Any subset of:

{ "groups": ["group_name"], "mail": "newinvitee@example.com" }

Set mail to null/empty to clear it. Fails with 400 if the token is no longer valid.

Response:

{ "results": { "token": "invite_token_string", "is_valid": true, "...": "..." } }

Revoke Invite Token

DELETE /api/user/invite/:tokenapp_sso_admin, or the app_sso_invite member who created the invite

URL Parameters: token — invite token

Response:

{ "results": true }

Add SSH Key

POST /api/user/key — Any authenticated user

Request:

{ "key": "ssh-rsa AAAAB3NzaC1yc2E... user@host" }

Response:

  • 200{ "message": true }
  • 400{ "message": "error description" }

Group Management Endpoints

Base path: /api/group

All endpoints require authentication.

List Groups

GET /api/group/ — Any authenticated user

Query Parameters:

  • detail (optional) — return full group objects
  • member (optional) — filter to groups containing this UID as a member

Response:

{ "results": [{ "cn": "group_name", "description": "Group description" }] }

Create Group

POST /api/group/app_sso_admin required

Request:

{ "name": "group_name", "description": "Group description" }

Response:

{ "results": { "cn": "group_name" }, "message": "group_name was added!" }

The authenticated user is automatically set as the group owner.


Get Group

GET /api/group/:name — Any authenticated user

Response:

{
  "results": {
    "cn": "group_name",
    "description": "Group description",
    "member": ["uid=user1,ou=people,dc=...", "uid=user2,ou=people,dc=..."],
    "owner": ["uid=owner,ou=people,dc=..."]
  }
}

Add Group Owner

PUT /api/group/owner/:group/:uidapp_sso_admin or group owner

Response:

{ "results": true, "message": "Added owner uid to group group." }

Remove Group Owner

DELETE /api/group/owner/:group/:uidapp_sso_admin or group owner

Response:

{ "results": true, "message": "Removed Owner uid from group group." }

Add User to Group

PUT /api/group/:group/:uidapp_sso_admin or group owner

Response:

{ "results": true, "message": "Added user uid to group group." }

Returns 409 if the user is already a member — common in practice, since groupOfNames requires at least one member and so seeds whoever created the group into it.


Remove User from Group

DELETE /api/group/:group/:uidapp_sso_admin or group owner

Response:

{ "results": true, "message": "Removed user uid from group group." }

Nest a Group Inside Another

PUT /api/group/:group/nested/:childapp_sso_admin or group owner

Makes :child a member of :group, so everyone in :child is a member of :group at any depth.

Response:

{ "results": { "cn": "group", "member": ["..."] }, "message": "Nested child inside group." }

Errors:

Status When
400 :group and :child are the same group
409 already nested, or the nesting would create a loop (:child already contains :group, directly or transitively)

Un-nest a Group

DELETE /api/group/:group/nested/:childapp_sso_admin or group owner

Response:

{ "results": { "cn": "group", "member": ["..."] }, "message": "Removed child from group." }

Errors:

Status When
409 :child is the only member — groupOfNames requires at least one

Effective Membership

GET /api/group/:group/effective — Any authenticated user

Who a group actually grants. direct is users listed on the group itself (never groups); nestedGroups is what is nested into it; effective is every user reachable through the whole chain.

Response:

{
  "results": {
    "cn": "app_gitea_access",
    "direct": ["cn=alice,ou=people,dc=example,dc=com"],
    "nestedGroups": [{ "cn": "developers", "dn": "cn=developers,ou=groups,dc=example,dc=com" }],
    "effective": ["cn=alice,ou=people,dc=example,dc=com", "cn=bob,ou=people,dc=example,dc=com"]
  }
}

Delete Group

DELETE /api/group/:groupapp_sso_admin or group owner

Response:

{ "removed": true, "results": { "cn": "group_name" }, "message": "Group group_name Deleted" }

Token Management Endpoints

Base path: /api/token

All endpoints require authentication.

List Token Types

GET /api/token/ — Any authenticated user

Response:

{ "results": ["InviteToken", "PasswordResetToken"] }

List Tokens by Type

GET /api/token/:name — Any authenticated user

Query Parameters: detail (optional) — include full token objects

Response:

{
  "results": [
    { "token": "token_string", "created_by": "username", "created_on": 1234567890000, "is_valid": true }
  ]
}

Get Specific Token

GET /api/token/:name/:token — Any authenticated user

Response:

{ "results": { "token": "token_string", "created_by": "username", "created_on": 1234567890000, "is_valid": true } }

Notification Endpoints

Base path: /api/notification

All endpoints require authentication and app_sso_admin membership.

Notifications send email blasts to filtered groups of users and record history.

Send Notification

POST /api/notificationapp_sso_admin required

Request:

{
  "subject": "Maintenance window tonight",
  "message": "<p>We will be performing maintenance starting at midnight.</p>",
  "filter_type": "group",
  "filter_value": "host_hec-bot_admin, app_sso_admin",
  "active_only": true
}

filter_type values:

Value Description
all_active All active (unlocked) users
all All users including inactive
group Members of one or more LDAP groups
users Specific users by UID

filter_value:

  • For group: comma-separated group names (e.g. "host_hec-bot_admin, app_sso_admin")
  • For users: JSON array of UIDs (e.g. '["wmantly","jsmith"]')
  • Unused for all_active and all

active_only (boolean, default false): when true with group or all filter types, only active (unlocked) users receive the notification. Has no effect on all_active (which is always active-only).

Response:

{
  "results": {
    "notification_id": "uuid",
    "created_by": "wmantly",
    "created_on": 1234567890000,
    "subject": "Maintenance window tonight",
    "message": "<p>...</p>",
    "filter_type": "group",
    "filter_value": "host_hec-bot_admin",
    "active_only": true,
    "status": "sent",
    "sent_count": 5,
    "failed_count": 0,
    "sent_at": 1234567890123
  }
}

External API usage example:

# Login once, store token
TOKEN=$(curl -s -X POST https://sso.example.com/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"uid":"monitor","password":"..."}' | jq -r .token)

# Send notification to a group
curl -X POST https://sso.example.com/api/notification \
  -H "auth-token: $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "subject": "hec-bot maintenance",
    "message": "<p>The hec-bot VM will restart at 3am.</p>",
    "filter_type": "group",
    "filter_value": "host_hec-bot_admin",
    "active_only": true
  }'

List Notification History

GET /api/notificationapp_sso_admin required

Response:

{
  "results": [
    {
      "notification_id": "uuid",
      "subject": "Maintenance window",
      "filter_type": "group",
      "filter_value": "host_hec-bot_admin",
      "active_only": true,
      "status": "sent",
      "sent_count": 5,
      "failed_count": 0,
      "created_by": "wmantly",
      "created_on": 1234567890000,
      "sent_at": 1234567890123
    }
  ]
}

Results are sorted by created_on descending (most recent first).


Get Notification Record

GET /api/notification/:idapp_sso_admin required

Response: { "results": { <notification object> } }


OAuth 2.0 / OpenID Connect

The SSO Manager acts as an OAuth 2.0 Authorization Server and OpenID Connect Provider.

Discovery

GET /.well-known/openid-configuration — No auth required

Returns the OIDC discovery document with endpoint URLs, supported scopes, and signing algorithms.


Authorization Endpoint

GET /oauth/authorize — No auth required (redirects to login if not authenticated)

Query Parameters:

  • response_type — Must be code
  • client_id — Registered OAuth client ID
  • redirect_uri — Must match a URI registered for the client, either exactly or against a registered wildcard pattern (* = one hostname label, ** = any number of labels)
  • scope — Space-separated: openid, profile, email
  • state — Opaque value returned unchanged in the redirect
  • code_challenge — PKCE challenge (SHA-256 of code_verifier, base64url-encoded)
  • code_challenge_method — Must be S256

Renders the consent screen.


Authorize (Issue Code)

Issues the authorization code after the user approves the consent form shown by the Authorization Endpoint above. This is called by the consent page itself (an authenticated request, via auth-token), not by the OAuth client directly.

POST /api/oauth/authorize — Auth required (auth-token header)

Request:

{
  "response_type": "code",
  "client_id": "uuid",
  "redirect_uri": "https://ha.example.com/auth/external/callback",
  "scope": "openid profile email",
  "state": "opaque-state-value",
  "code_challenge": "pkce-challenge",
  "code_challenge_method": "S256"
}

Only scopes the client is actually registered for are granted, even if more are requested. If the client has allowed_groups set, the authenticated user must be a member of at least one of those groups or the request is rejected with 403.

Response:

{ "redirect_url": "https://ha.example.com/auth/external/callback?code=<code>&state=<state>" }

The caller (the consent page) redirects the browser to redirect_url, which completes the flow described in the Authorization Endpoint section above.


Token Endpoint

POST /oauth/token — No auth required (client authenticates via credentials)

Content-Type: application/x-www-form-urlencoded or application/json

Client Authentication: client_id + client_secret in the request body, or HTTP Basic Auth.

Authorization Code Grant

grant_type=authorization_code
&code=<auth_code>
&redirect_uri=<redirect_uri>
&client_id=<client_id>
&client_secret=<client_secret>
&code_verifier=<pkce_verifier>

Refresh Token Grant

grant_type=refresh_token
&refresh_token=<refresh_token>
&client_id=<client_id>
&client_secret=<client_secret>

Refresh tokens are rotated on each use — the old token is invalidated and a new one is returned.

Response:

{
  "access_token": "uuid",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "uuid",
  "id_token": "<jwt>"
}

UserInfo Endpoint

GET /oauth/userinfo — Bearer token required (Authorization: Bearer <access_token>)

Response (claims vary by granted scopes):

{
  "sub": "username",
  "name": "First Last",
  "given_name": "First",
  "family_name": "Last",
  "preferred_username": "username",
  "email": "user@example.com"
}

OAuth Client Management

Base path: /api/oauth/client

All endpoints require authentication and app_sso_oauth_admin membership.

List Clients

GET /api/oauth/client/

Response:

{
  "results": [
    {
      "client_id": "uuid",
      "name": "Home Assistant",
      "description": "Home automation",
      "redirect_uris": ["https://ha.example.com/auth/external/callback"],
      "scopes": ["openid", "profile", "email"],
      "allowed_groups": [],
      "token_lifetime": { "access_token": 3600, "refresh_token": 2592000 },
      "created_by": "wmantly",
      "created_on": 1234567890000
    }
  ]
}

Register Client

POST /api/oauth/client/

Request:

{
  "name": "Home Assistant",
  "description": "Home automation dashboard",
  "redirect_uris": ["https://ha.example.com/auth/external/callback"],
  "scopes": ["openid", "profile", "email"],
  "allowed_groups": [],
  "token_lifetime": { "access_token": 3600, "refresh_token": 2592000 }
}

redirect_uris may also be a newline-separated string. scopes may also be a space-separated string. allowed_groups restricts the client to members of the listed SSO groups (empty/omitted = any valid user).

Response:

{
  "results": { "client_id": "uuid", "name": "Home Assistant" },
  "client_secret": "raw-secret-shown-once",
  "message": "OAuth client 'Home Assistant' created. Save the client secret — it will not be shown again."
}

The client_secret is shown only once. Store it immediately.


Get Client

GET /api/oauth/client/:client_id

Response: { "results": { <client object> } }


Update Client

PUT /api/oauth/client/:client_id

Request: Any subset of name, description, redirect_uris, scopes, allowed_groups, token_lifetime, is_valid.

Response: { "results": { <updated client> }, "message": "..." }


Delete Client

DELETE /api/oauth/client/:client_id

Response: { "client_id": "uuid", "message": "OAuth client '...' deleted." }


Rotate Client Secret

POST /api/oauth/client/:client_id/rotate

Response:

{
  "client_secret": "new-raw-secret-shown-once",
  "message": "Client secret rotated for '...'. Save it — it will not be shown again."
}

The old secret is invalidated immediately. The new secret is shown only once.


Token Lifetimes

Configurable per-client via token_lifetime. Global defaults (in seconds):

{ "access_token": 3600, "refresh_token": 2592000 }

Plugin Endpoints

Base path: /api/plugins

All endpoints require authentication and app_sso_admin, app_sso_directory_admin, or app_super_admin membership. Secret field values are always returned masked (********); they are stored in OpenBao at secret/plugins/<instance-id>/conf, never in the database row. See Plugins.

List Plugin Types

GET /api/plugins/types

Returns the installed plugin types and their configSchema (used to build the create-instance form).

Response:

{
  "results": [
    {
      "type": "proxmox",
      "category": "discovery",
      "name": "Proxmox VE",
      "description": "Discover VMs, containers, and hypervisor nodes from a Proxmox VE API endpoint.",
      "configSchema": [
        { "key": "url", "label": "API URL", "type": "url", "required": true },
        { "key": "tokenId", "label": "Token ID", "type": "text", "required": true },
        { "key": "tokenSecret", "label": "Token Secret", "type": "password", "required": true, "secret": true }
      ]
    }
  ]
}

List Plugin Instances

GET /api/plugins/

Response: { "results": [ { "id", "pluginType", "category", "name", "slug", "enabled", "cron", "config", "secrets": {…masked…}, "lastRunAt", "lastStatus", "lastError" } ] }


Get One Instance

GET /api/plugins/:id — same shape as a list entry.


Create Instance

POST /api/plugins/

config is a flat object of all field values (secret and non-secret); the server splits it — non-secret fields go to the DB row, secret fields to OpenBao. Creating an enabled instance schedules it and kicks one immediate run. slug is the discovery source name (lowercase letters/digits/_/-, max 64, unique).

Request:

{
  "pluginType": "proxmox",
  "name": "Proxmox — Home Lab",
  "slug": "proxmox-homelab",
  "cron": "0 * * * *",
  "config": { "url": "https://pve:8006", "tokenId": "u@pam!t", "tokenSecret": "secret-value" }
}

Errors: 400 if the plugin type is unknown, the slug is malformed/duplicated, or a required field is missing; 400 with an OpenBao hint if writing the secret fails (re-run ./setup.sh with theta-suite ≥ v1.30.1).


Update Instance

PUT /api/plugins/:id — update name, cron, enabled, and non-secret config. Secret fields are changed via PUT /:id/secrets. Re-schedules if cron or enabled changed.


Update Secrets

PUT /api/plugins/:id/secrets — body is a flat object of secret field values. Blank/******** values are ignored (kept as-is).


Test Instance

POST /api/plugins/:id/test — runs the plugin's validate. Returns { "ok": true } or 400 { "ok": false, "error": "..." }.


Load / Unload / Run Now

  • POST /api/plugins/:id/load — enable + schedule + run now.
  • POST /api/plugins/:id/unload — unschedule + disable.
  • POST /api/plugins/:id/run — enqueue one immediate run (regardless of enabled).

Last Run Status

GET /api/plugins/:id/runs{ "results": { "lastRunAt", "lastStatus", "lastError" } } (lastStatus is ok | error | running).


Delete Instance

DELETE /api/plugins/:id — unschedules, removes the OpenBao secret namespace, and deletes the row.

Configuration Endpoints

Base path: /api/conf

All endpoints require authentication and app_sso_admin membership. Runtime configuration (SMTP, discovery, OAuth) is stored in OpenBao at secret/sso-manager/conf and overlaid onto the live app config; changes take effect immediately and persist across restarts. Secret fields (smtp.pass, oauth.jwtSecret) are always returned masked (********); submit a blank or ******** value to keep the current stored secret, or a new non-blank value to replace it.

Get Configuration

GET /api/conf — returns the editable config groups (smtp, discovery, oauth) with secret fields masked to ********.

Response:

{
  "smtp": { "host": "smtp.example.com", "port": 587, "secure": false, "user": "noreply@example.com", "pass": "********", "from": "SSO Manager <noreply@example.com>" },
  "discovery": { },
  "oauth": { "issuer": "https://sso.example.com", "jwtSecret": "********", "token_lifetime": { "access_token": 3600, "refresh_token": 2592000 } }
}

Save Configuration

POST /api/conf — deep-merges the submitted groups into secret/sso-manager/conf (per-key shallow merge of nested objects) and re-applies them to the live config. A blank or ******** value for smtp.pass or oauth.jwtSecret preserves the stored secret.

Request:

{
  "smtp": { "host": "smtp.example.com", "port": 587, "secure": false, "user": "noreply@example.com", "pass": "********", "from": "SSO Manager <noreply@example.com>" },
  "oauth": { "issuer": "https://sso.example.com", "token_lifetime": { "access_token": 3600, "refresh_token": 2592000 } }
}

Response: { "success": true }

Error Responses

All endpoints return errors in this format:

{ "name": "ErrorName", "message": "Error message description" }

HTTP Status Codes

Code Meaning
200 Success
400 Bad Request — invalid input
401 Unauthorized — auth required, token invalid, or insufficient permission
404 Not Found
429 Too Many Requests — rate limit exceeded
500 Internal Server Error
502 Bad Gateway — upstream service failure (e.g. SMS delivery)