Organization Settings
GET /orgs/{orgId}/api/v1/admin/organization
PUT /orgs/{orgId}/api/v1/admin/organization
PATCH /orgs/{orgId}/api/v1/admin/organization
GET /orgs/{orgId}/api/v1/admin/settings/auth
PUT /orgs/{orgId}/api/v1/admin/settings/auth
PATCH /orgs/{orgId}/api/v1/admin/settings/auth
GET /orgs/{orgId}/api/v1/admin/settings/branding
PUT /orgs/{orgId}/api/v1/admin/settings/branding
PATCH /orgs/{orgId}/api/v1/admin/settings/branding
GET /orgs/{orgId}/api/v1/admin/settings/scim
PUT /orgs/{orgId}/api/v1/admin/settings/scim
PATCH /orgs/{orgId}/api/v1/admin/settings/scim
All endpoints on this page need an API key or a Bearer token with admin:settings:read
(GET) or admin:settings:write (PUT/PATCH), and a permission: settings.view to read,
settings.manage to change the organization and its authentication settings,
branding.view/branding.edit for branding and scim.view/scim.edit for SCIM. See
Required permissions.
Use this page to read your organization's profile, change its name and the handful of organization-level settings that are safe to change over the API, and manage the authentication, branding and SCIM blocks. The general, security and email blocks are on the Settings page.
Secrets in responses
No settings response on this page returns credential material. Every non-empty value under a
secret-looking key (names ending in secret, password, token, tokens, hash, key,
credentials, or containing private_key; public_key, *_preview and *_at stay readable)
is replaced with a marker:
{ "bearer_token_hash": { "configured": true, "redacted": true } }
You can PATCH a fetched payload back unchanged: a marker leaves the stored value as it is.
To change a secret, send the new value in place of the marker.
Get Organization Info
- curl
- Node
- Python
- Go
curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/organization \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/organization", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/organization",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/organization", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
The response is wrapped in a data envelope. The organization id is an integer; settings may be null when no organization-level settings have been saved.
{
"data": {
"id": 4,
"slug": "acme-corp",
"name": "ACME Corp",
"status": "active",
"settings": null,
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-03-01T08:00:00Z"
}
}
Update Organization
- curl
- Node
- Python
- Go
curl -X PATCH https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/organization \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{ "name": "ACME Corporation" }'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/organization", {
method: "PATCH",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({ "name": "ACME Corporation" })
});
console.log(await resp.json());
import os, requests
resp = requests.patch(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/organization",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={ "name": "ACME Corporation" }
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{ "name": "ACME Corporation" }`)
req, _ := http.NewRequest("PATCH", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/organization", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Writable settings keys
PUT and PATCH behave the same: name replaces the organization name, and settings is
merged into the stored settings one top-level key at a time. Only these top-level keys are
accepted:
| Key | Type | What it controls |
|---|---|---|
display_name (alias displayName) | string | Name shown on hosted pages |
timezone | string | IANA time zone |
locale | string | Default locale |
security | object | OAuth hardening switches such as dpop_require_nonce; see Security settings |
email | object | Outbound e-mail settings; see Email settings |
audit_log_retention_days | integer | Audit log retention |
audit_log_auto_delete | boolean | Purge audit logs past retention automatically |
agent_governance | object | Agent governance defaults; see Governance |
Everything else is refused. The request fails as a whole, nothing is written, and the
response lists every offending path in details.rejected_keys:
- any top-level key not in the table (for example
sandbox,allow_shared_signing_key,include_global_jwks,allow_root_admin_login,slug,status); - any key at any depth whose name ends in
_hash,_secret,_token,_keyor_password(for examplesecurity.scim_token).
curl -X PATCH https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/organization \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{ "settings": { "timezone": "UTC", "sandbox": { "is_sandbox": false } } }'
{
"error": "invalid_request",
"message": "Unsupported settings key(s): sandbox. Writable keys are: display_name, displayName, timezone, locale, security, email, audit_log_retention_days, audit_log_auto_delete, agent_governance.",
"status": 400,
"details": {
"rejected_keys": ["sandbox"]
}
}
Platform-derived flags (sandbox status, shared signing-key opt-ins, JWKS publication) and every stored secret are never client-writable through this endpoint.
allow_root_admin_login (read-only)
By default LumoAuth operators cannot sign in through your organization's hosted OAuth login
page; the page only knows your organization's own users. The allow_root_admin_login switch
re-enables that fallback, for example during a support engagement. It is not writable
through this API or the organization portal: a PATCH containing it returns
400 invalid_request with rejected_keys. It is changed only by LumoAuth operators, at your
request. Each change is audited in your log as admin.tenant.root_admin_login.enabled /
.disabled, and every such login as oauth.root_fallback_login at critical severity.
security.dpop_require_nonce
security is one of the writable keys, so { "settings": { "security": { "dpop_require_nonce": true } } }
is accepted here. Be aware that this endpoint replaces the entire security object with
what you send. To change one switch without touching the rest, use
PATCH /settings/security, which merges key by key.
What the switch does is documented under Security settings fields.
Common mistakes
400 invalid_requestwithrejected_keys: you sent a key outside the table above, or a nested key with a secret-like suffix. Remove it; the platform manages those values.- A
securityswitch disappeared after aPATCH /organization: thesecurityobject was replaced wholesale. Resend the full object, or usePATCH /settings/securitynext time. metadatais silently ignored: the organization has no free-form metadata field. Usesettingswith an allow-listed key, or store application data on your side.403 forbidden: the credential lacksadmin:settings:write(oradmin:settings:readforGET), or the Bearer token has no admin scope. See Authentication errors.
Sign-in settings
Control how users authenticate within the organization.
- curl
- Node
- Python
- Go
curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/auth \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/auth", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/auth",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/auth", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
The response is wrapped in a data envelope. Auth settings are a flat object (there are no nested session/login_flow groups); the one nested object is mfa_policy, present once an MFA policy has been saved. session_timeout is in seconds.
Manage MFA with the dedicated MFA policy API (GET/PUT /admin/policies/mfa), which validates and clamps values, stamps the grace-period start and audits the change. mfa_required, mfa_methods and mfa_sms_enabled are legacy keys kept in sync with the policy. Sending mfa_required, mfa_sms_enabled or mfa_policy to this endpoint is applied as an MFA policy change (mfa_required: true → required, false → risk_based; mfa_sms_enabled toggles sms_otp).
{
"data": {
"passkey_enabled": false,
"biometric_enabled": false,
"mfa_required": false,
"mfa_methods": [],
"push_auth_required": false,
"social_login_enabled": false,
"social_providers": [],
"password_min_length": 8,
"password_require_special": false,
"password_policy": {
"template": "fair",
"min_length": 8,
"require_special": false,
"require_numbers": false,
"require_uppercase": false,
"require_lowercase": false
},
"password_rotation_policy": {
"enabled": false,
"max_age_days": 90,
"notify_before_days": 7,
"history_count": 0
},
"no_mfa_action": "allow",
"session_timeout": 1209600,
"dynamic_client_registration_enabled": false,
"progressive_profiling": {
"enabled": false,
"rules": []
}
}
}
- curl
- Node
- Python
- Go
curl -X PATCH https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/auth \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{
"passkey_enabled": true,
"session_timeout": 28800
}'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/auth", {
method: "PATCH",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({
"passkey_enabled": true,
"session_timeout": 28800
})
});
console.log(await resp.json());
import os, requests
resp = requests.patch(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/auth",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={
"passkey_enabled": True,
"session_timeout": 28800
}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{
"passkey_enabled": true,
"session_timeout": 28800
}`)
req, _ := http.NewRequest("PATCH", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/auth", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Branding Settings
Customize the login page appearance for the organization.
- curl
- Node
- Python
- Go
curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/branding \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/branding", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/branding",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/branding", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
The response is wrapped in a data envelope. The gradient is a single CSS string (background_gradient), and the welcome copy is split into welcome_title / welcome_subtitle.
{
"data": {
"enabled": false,
"logo_url": "https://example.com/logo.png",
"background_color": "#ffffff",
"primary_color": "#667eea",
"button_text": "Sign in",
"show_tenant_name": true,
"custom_css": null,
"custom_html": null,
"theme": "auto",
"self_registration_enabled": true,
"layout": "centered",
"background_type": "solid",
"background_image_url": null,
"background_gradient": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
"background_overlay_opacity": 50,
"show_background_pattern": true,
"welcome_title": null,
"welcome_subtitle": null,
"terms_url": null,
"privacy_url": null,
"footer_text": null,
"show_powered_by": true,
"input_style": "rounded",
"button_style": "filled",
"form_card_style": "card",
"magic_link_enabled": true,
"magic_link_only": false,
"magic_link_expiry": 900,
"email_first_mode": true
}
}
SCIM Settings
Configure inbound SCIM 2.0 provisioning from your identity provider.
- curl
- Node
- Python
- Go
curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/scim \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/scim", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/scim",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/settings/scim", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
The response is wrapped in a data envelope and splits configuration into inbound (your IdP provisions into LumoAuth) and outbound (LumoAuth pushes changes to a downstream SCIM endpoint). Inbound provisioning is controlled by the allow_user_creation / allow_user_updates / allow_user_deletion / allow_group_operations flags, and auto_activate_users: false creates provisioned users inactive.
- Inbound
bearer_tokenis write-only. Send a new token ininbound.bearer_tokento set it; only its SHA-256 hash (bearer_token_hash, returned redacted), a non-reversiblebearer_token_previewandtoken_created_atare stored and returned. - Outbound secrets (
bearer_token,basic_password,oauth2_client_secret) are encrypted at rest and returned redacted. Changingendpoint_urloroauth2_token_urlrequires re-entering the secret in the same request. - Outbound authentication.
auth_typeisbearer,basicoroauth2(client credentials:oauth2_client_id,oauth2_client_secret,oauth2_token_url, optionaloauth2_scope). - Outbound sync options.
push_group_changes,sync_on_loginandbatch_sync_enabledare booleans;batch_sync_intervalishourly,dailyorweekly. Any other value is rejected with400. See Groups, login sync and batch sync. - Server-owned keys.
last_sync_*(last connection test) andlast_batch_sync_at/last_batch_sync_status/last_batch_sync_error/last_batch_sync_counts(last batch run) are returned but ignored on write.
{
"data": {
"inbound": {
"enabled": false,
"bearer_token_hash": { "configured": true, "redacted": true },
"bearer_token_preview": "lmk_...3f9a1c2e",
"token_created_at": "2026-09-28 10:15:00",
"allow_user_creation": true,
"allow_user_updates": true,
"allow_user_deletion": true,
"allow_group_operations": true,
"protected_attributes": {
"email": false,
"name": false,
"active": false,
"roles": true,
"permissions": true
},
"attribute_mapping": {
"userName": "email",
"name.givenName": "givenName",
"name.familyName": "familyName",
"displayName": "name",
"active": "isActive"
},
"auto_activate_users": true,
"deprovisioning_action": "soft_delete"
},
"outbound": {
"enabled": false,
"endpoint_url": null,
"bearer_token": null,
"auth_type": "bearer",
"oauth2_client_id": null,
"oauth2_client_secret": null,
"oauth2_token_url": null,
"oauth2_scope": null,
"verify_tls": true,
"push_user_creates": true,
"push_user_updates": true,
"push_user_deletes": true,
"push_group_changes": false,
"sync_on_login": false,
"batch_sync_enabled": false,
"batch_sync_interval": "daily",
"attribute_mapping": {
"email": "userName",
"givenName": "name.givenName",
"familyName": "name.familyName",
"name": "displayName",
"isActive": "active"
},
"last_sync_at": null,
"last_sync_status": null,
"last_sync_error": null,
"last_batch_sync_at": null,
"last_batch_sync_status": null,
"last_batch_sync_error": null,
"last_batch_sync_counts": null
}
}
}