Skip to main content

Tenant Settings API

Settings endpoints require a Bearer token with the appropriate permission. All endpoints are scoped to the tenant identified by the X-Tenant-ID header.

Permission convention: settings:read grants access to all GET endpoints. settings:write grants access to all mutating endpoints.

Base path: https://api.ithbat.io/api/v1/


General Settings

GET /api/v1/tenant/settings

Permission: settings:read

Return the full configuration snapshot for the current tenant, including general, branding, and security settings in a single response.

curl "https://api.ithbat.io/api/v1/tenant/settings" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200

{
"success": true,
"data": {
"settings": {
"general": {
"name": "Acme Corporation",
"locale": "ar-SA",
"timezone": "Asia/Riyadh",
"direction": "rtl"
},
"branding": {
"logoUrl": "https://cdn.ithbat.io/tenants/3e7a9f12/logo.png",
"faviconUrl": "https://cdn.ithbat.io/tenants/3e7a9f12/favicon.ico",
"primaryColor": "#0B7285",
"accentColor": "#C8943F",
"fontFamily": "Almarai"
},
"security": {
"sessionTimeoutMinutes": 60,
"mfaEnforced": true,
"allowedMfaMethods": ["totp", "sms", "email"],
"ssoOnly": false,
"deviceTrustEnabled": true,
"deviceTrustDurationDays": 30
}
}
}
}

PATCH /api/v1/tenant/settings/general

Permission: settings:write

Update general tenant settings. Only fields included in the request body are changed.

Request Body — All fields optional:

FieldTypeDescription
namestringDisplay name of the tenant
localestringBCP 47 locale tag (e.g., ar-SA, en-US)
timezonestringIANA timezone name (e.g., Asia/Riyadh, UTC)
directionstringText direction: ltr or rtl
curl -X PATCH "https://api.ithbat.io/api/v1/tenant/settings/general" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corporation",
"locale": "ar-SA",
"timezone": "Asia/Riyadh",
"direction": "rtl"
}'

Response 200

{
"success": true,
"data": {
"general": {
"name": "Acme Corporation",
"locale": "ar-SA",
"timezone": "Asia/Riyadh",
"direction": "rtl"
}
}
}

PATCH /api/v1/tenant/settings/branding

Permission: settings:write

Update tenant branding. Changes take effect immediately for all users on the next page load.

Request Body — All fields optional:

FieldTypeDescription
logoUrlstringAbsolute URL to the tenant logo image
faviconUrlstringAbsolute URL to the tenant favicon
primaryColorstringHex color for primary UI elements (e.g., #0B7285)
accentColorstringHex color for accent UI elements (e.g., #C8943F)
fontFamilystringFont family name: Inter, Outfit, Cairo, or Almarai
curl -X PATCH "https://api.ithbat.io/api/v1/tenant/settings/branding" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"logoUrl": "https://cdn.ithbat.io/tenants/3e7a9f12/logo.png",
"primaryColor": "#0B7285",
"accentColor": "#C8943F",
"fontFamily": "Almarai"
}'

Response 200 — Returns the updated branding object inside the standard envelope.

PATCH /api/v1/tenant/settings/security

Permission: settings:write

Update security-related settings.

Request Body — All fields optional:

FieldTypeDescription
sessionTimeoutMinutesintegerIdle session timeout in minutes (min: 5, max: 1440)
mfaEnforcedbooleanRequire MFA for all users
allowedMfaMethodsstring[]Permitted MFA methods: totp, sms, email, passkey
ssoOnlybooleanBlock local password login — SSO only
deviceTrustEnabledbooleanEnable remembered-device feature
deviceTrustDurationDaysintegerDays a trusted device remains valid
curl -X PATCH "https://api.ithbat.io/api/v1/tenant/settings/security" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"sessionTimeoutMinutes": 60,
"mfaEnforced": true,
"allowedMfaMethods": ["totp", "passkey"],
"ssoOnly": false,
"deviceTrustEnabled": true,
"deviceTrustDurationDays": 30
}'

Response 200 — Returns the updated security object inside the standard envelope.


Custom Domain

GET /api/v1/tenant/custom-domain

Permission: settings:read

Return the current custom domain configuration for the tenant.

curl "https://api.ithbat.io/api/v1/tenant/custom-domain" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200

{
"success": true,
"data": {
"customDomain": {
"domain": "sso.acme.sa",
"status": "verified",
"cnameTarget": "tenants.ithbat.io",
"sslStatus": "active",
"verifiedAt": "2026-02-10T08:45:00Z"
}
}
}

Domain status values: pending_verification, verified, ssl_provisioning, active, failed.

POST /api/v1/tenant/custom-domain

Permission: settings:write

Set a new custom domain. After setting, the domain must be verified before it becomes active.

Request Body

FieldTypeRequiredDescription
domainstringYesFully qualified domain name (e.g., sso.acme.sa)
curl -X POST "https://api.ithbat.io/api/v1/tenant/custom-domain" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"domain": "sso.acme.sa"
}'

Response 201

{
"success": true,
"data": {
"customDomain": {
"domain": "sso.acme.sa",
"status": "pending_verification",
"cnameTarget": "tenants.ithbat.io",
"sslStatus": null,
"verifiedAt": null
}
}
}

Add a CNAME record pointing sso.acme.sa to tenants.ithbat.io at your DNS provider, then call the verify endpoint.

POST /api/v1/tenant/custom-domain/verify

Permission: settings:write

Trigger a DNS verification check for the configured custom domain. Returns the updated status immediately after the probe completes.

curl -X POST "https://api.ithbat.io/api/v1/tenant/custom-domain/verify" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200 — Verification succeeded

{
"success": true,
"data": {
"customDomain": {
"domain": "sso.acme.sa",
"status": "ssl_provisioning",
"cnameTarget": "tenants.ithbat.io",
"sslStatus": "pending",
"verifiedAt": "2026-03-19T12:00:00Z"
}
}
}

Response 422 — DNS not yet propagated

{
"success": false,
"error": {
"code": "DOMAIN_VERIFICATION_FAILED",
"message": "CNAME record for sso.acme.sa does not resolve to tenants.ithbat.io. DNS propagation can take up to 48 hours."
}
}

DELETE /api/v1/tenant/custom-domain

Permission: settings:write

Remove the custom domain from the tenant. The tenant reverts to the default {slug}.ithbat.io domain immediately.

curl -X DELETE "https://api.ithbat.io/api/v1/tenant/custom-domain" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200

{
"success": true,
"data": null
}

Password Policy

GET /api/v1/tenant/password-policy

Permission: settings:read

Return the current password policy for the tenant.

curl "https://api.ithbat.io/api/v1/tenant/password-policy" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200

{
"success": true,
"data": {
"passwordPolicy": {
"minLength": 10,
"requireUppercase": true,
"requireLowercase": true,
"requireNumbers": true,
"requireSymbols": true,
"preventPasswordReuse": 10,
"maxAgeDays": 90,
"lockoutThreshold": 5,
"lockoutDurationMinutes": 30
}
}
}

PUT /api/v1/tenant/password-policy

Permission: settings:write

Replace the entire password policy. All fields must be provided.

Request Body

FieldTypeRequiredDescription
minLengthintegerYesMinimum password length (min: 8, max: 128)
requireUppercasebooleanYesRequire at least one uppercase letter
requireLowercasebooleanYesRequire at least one lowercase letter
requireNumbersbooleanYesRequire at least one digit
requireSymbolsbooleanYesRequire at least one special character
preventPasswordReuseintegerYesNumber of previous passwords users cannot reuse (0 to disable)
maxAgeDaysintegerYesForce password reset after this many days (0 to disable)
lockoutThresholdintegerYesFailed attempts before account lockout (0 to disable)
lockoutDurationMinutesintegerYesDuration of lockout in minutes
curl -X PUT "https://api.ithbat.io/api/v1/tenant/password-policy" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"minLength": 10,
"requireUppercase": true,
"requireLowercase": true,
"requireNumbers": true,
"requireSymbols": true,
"preventPasswordReuse": 10,
"maxAgeDays": 90,
"lockoutThreshold": 5,
"lockoutDurationMinutes": 30
}'

Response 200 — Returns the updated passwordPolicy object inside the standard envelope.


IP Whitelist

GET /api/v1/tenant/security/ip-whitelist

Permission: settings:read

Return the current IP whitelist configuration.

curl "https://api.ithbat.io/api/v1/tenant/security/ip-whitelist" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200

{
"success": true,
"data": {
"ipWhitelist": {
"enabled": true,
"entries": [
{
"cidr": "197.32.0.0/16",
"label": "Riyadh Office",
"addedAt": "2026-01-20T09:00:00Z"
},
{
"cidr": "41.128.200.0/24",
"label": "Cairo Branch",
"addedAt": "2026-02-05T14:30:00Z"
}
]
}
}
}

PUT /api/v1/tenant/security/ip-whitelist

Permission: settings:write

Replace the entire IP whitelist. Setting enabled to false disables enforcement without removing the entries.

Request Body

FieldTypeRequiredDescription
enabledbooleanYesEnable or disable IP whitelist enforcement
entriesarrayYesList of CIDR entry objects (see below)

Each entry in entries:

FieldTypeRequiredDescription
cidrstringYesIPv4 or IPv6 CIDR block (e.g., 197.32.0.0/16)
labelstringNoHuman-readable label for this range
curl -X PUT "https://api.ithbat.io/api/v1/tenant/security/ip-whitelist" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"entries": [
{ "cidr": "197.32.0.0/16", "label": "Riyadh Office" },
{ "cidr": "41.128.200.0/24", "label": "Cairo Branch" }
]
}'

Response 200 — Returns the updated ipWhitelist object inside the standard envelope.

POST /api/v1/tenant/security/ip-whitelist/test

Permission: settings:write

Test whether a specific IP address would be permitted under the current whitelist rules without changing any configuration.

Request Body

FieldTypeRequiredDescription
ipstringYesIPv4 or IPv6 address to test
curl -X POST "https://api.ithbat.io/api/v1/tenant/security/ip-whitelist/test" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"ip": "197.32.44.12"
}'

Response 200

{
"success": true,
"data": {
"ip": "197.32.44.12",
"allowed": true,
"matchedEntry": {
"cidr": "197.32.0.0/16",
"label": "Riyadh Office"
}
}
}

IdP Configurations

External Identity Provider (IdP) connections enable OIDC-based social and enterprise login for the tenant's users.

GET /api/v1/tenant/idp-configs

Permission: settings:read

List all IdP configurations for the tenant.

curl "https://api.ithbat.io/api/v1/tenant/idp-configs" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200

{
"success": true,
"data": {
"idpConfigs": [
{
"id": "idp-f1e2d3c4-b5a6-7890-cdef-012345678901",
"tenantId": "3e7a9f12-4b2c-4d8e-a1f0-9c2b3d4e5f6a",
"name": "Microsoft Azure AD",
"provider": "azure-ad",
"clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"discoveryUrl": "https://login.microsoftonline.com/org-tenant-id/v2.0/.well-known/openid-configuration",
"scopes": ["openid", "profile", "email"],
"enabled": true,
"createdAt": "2026-01-10T08:00:00Z",
"updatedAt": "2026-02-14T11:00:00Z"
}
],
"total": 1
}
}

GET /api/v1/tenant/idp-configs/{id}

Permission: settings:read

Get a single IdP configuration by ID.

curl "https://api.ithbat.io/api/v1/tenant/idp-configs/idp-f1e2d3c4-b5a6-7890-cdef-012345678901" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200 — Returns the IdP config object inside the standard envelope.

POST /api/v1/tenant/idp-configs

Permission: settings:write

Create a new IdP configuration.

Request Body

FieldTypeRequiredDescription
namestringYesDisplay name for this connection
providerstringYesProvider type: azure-ad, google, okta, generic-oidc
clientIdstringYesOAuth2 client ID issued by the IdP
clientSecretstringYesOAuth2 client secret (stored encrypted, never returned)
discoveryUrlstringYesOIDC discovery endpoint URL
scopesstring[]NoAdditional OIDC scopes (default: ["openid", "profile", "email"])
enabledbooleanNoEnable immediately (default: true)
curl -X POST "https://api.ithbat.io/api/v1/tenant/idp-configs" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"name": "Microsoft Azure AD",
"provider": "azure-ad",
"clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"clientSecret": "your-client-secret",
"discoveryUrl": "https://login.microsoftonline.com/org-tenant-id/v2.0/.well-known/openid-configuration",
"scopes": ["openid", "profile", "email"],
"enabled": true
}'

Response 201 — Returns the created IdP config object. clientSecret is not included in the response.

PUT /api/v1/tenant/idp-configs/{id}

Permission: settings:write

Replace an existing IdP configuration. All fields must be provided.

Request Body — Same fields as POST /api/v1/tenant/idp-configs. Omitting clientSecret preserves the existing stored secret.

curl -X PUT "https://api.ithbat.io/api/v1/tenant/idp-configs/idp-f1e2d3c4-b5a6-7890-cdef-012345678901" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"name": "Microsoft Azure AD",
"provider": "azure-ad",
"clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"discoveryUrl": "https://login.microsoftonline.com/org-tenant-id/v2.0/.well-known/openid-configuration",
"scopes": ["openid", "profile", "email", "offline_access"],
"enabled": true
}'

Response 200 — Returns the updated IdP config object.

DELETE /api/v1/tenant/idp-configs/{id}

Permission: settings:write

Delete an IdP configuration. Users who signed up exclusively through this IdP will retain their accounts but will be unable to log in until an alternative credential is set.

curl -X DELETE "https://api.ithbat.io/api/v1/tenant/idp-configs/idp-f1e2d3c4-b5a6-7890-cdef-012345678901" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200

{
"success": true,
"data": null
}

POST /api/v1/tenant/idp-configs/{id}/test

Permission: settings:write

Test connectivity to the IdP by validating the discovery URL and verifying that the stored client credentials are accepted. No authentication flow is initiated for end users.

curl -X POST "https://api.ithbat.io/api/v1/tenant/idp-configs/idp-f1e2d3c4-b5a6-7890-cdef-012345678901/test" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200 — Connection successful

{
"success": true,
"data": {
"reachable": true,
"discoveryValid": true,
"credentialsValid": true,
"issuer": "https://login.microsoftonline.com/org-tenant-id/v2.0",
"testedAt": "2026-03-19T13:00:00Z"
}
}

Response 200 — Connection failed

{
"success": true,
"data": {
"reachable": true,
"discoveryValid": true,
"credentialsValid": false,
"issuer": "https://login.microsoftonline.com/org-tenant-id/v2.0",
"error": "Client authentication failed — check client ID and secret.",
"testedAt": "2026-03-19T13:00:00Z"
}
}

SAML Configurations

SAML 2.0 Service Provider (SP) configurations enable enterprise SSO integrations via SAML assertions.

GET /api/v1/tenant/saml/configs

Permission: settings:read

List all SAML configurations for the tenant.

curl "https://api.ithbat.io/api/v1/tenant/saml/configs" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200

{
"success": true,
"data": {
"samlConfigs": [
{
"id": "saml-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"tenantId": "3e7a9f12-4b2c-4d8e-a1f0-9c2b3d4e5f6a",
"name": "Acme Internal IdP",
"entityId": "https://sso.acme.sa/saml/metadata",
"ssoUrl": "https://sso.acme.sa/saml/sso",
"sloUrl": "https://sso.acme.sa/saml/slo",
"signRequests": true,
"wantAssertionsSigned": true,
"nameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"attributeMapping": {
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
"firstName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
"lastName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
},
"enabled": true,
"createdAt": "2026-01-20T10:00:00Z",
"updatedAt": "2026-02-01T09:30:00Z"
}
],
"total": 1
}
}

GET /api/v1/tenant/saml/configs/{id}

Permission: settings:read

Get a single SAML configuration by ID. Includes the IdP signing certificate in PEM format.

curl "https://api.ithbat.io/api/v1/tenant/saml/configs/saml-a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200 — Returns the full SAML config object including idpCertificate (PEM string).

POST /api/v1/tenant/saml/configs

Permission: settings:write

Create a new SAML configuration.

Request Body

FieldTypeRequiredDescription
namestringYesDisplay name for this SAML connection
entityIdstringYesIdP entity ID (from IdP metadata)
ssoUrlstringYesIdP Single Sign-On URL
sloUrlstringNoIdP Single Logout URL
idpCertificatestringYesIdP signing certificate in PEM format
signRequestsbooleanNoSign outgoing SAML requests (default: true)
wantAssertionsSignedbooleanNoRequire signed assertions (default: true)
nameIdFormatstringNoNameID format URN (default: emailAddress)
attributeMappingobjectNoMap Ithbat user fields to SAML attribute names
enabledbooleanNoEnable immediately (default: true)
curl -X POST "https://api.ithbat.io/api/v1/tenant/saml/configs" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Internal IdP",
"entityId": "https://sso.acme.sa/saml/metadata",
"ssoUrl": "https://sso.acme.sa/saml/sso",
"sloUrl": "https://sso.acme.sa/saml/slo",
"idpCertificate": "-----BEGIN CERTIFICATE-----\nMIIBkTCB+...\n-----END CERTIFICATE-----",
"signRequests": true,
"wantAssertionsSigned": true,
"nameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"attributeMapping": {
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
"firstName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
"lastName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
},
"enabled": true
}'

Response 201 — Returns the created SAML config object.

PUT /api/v1/tenant/saml/configs/{id}

Permission: settings:write

Replace an existing SAML configuration. All fields must be provided.

curl -X PUT "https://api.ithbat.io/api/v1/tenant/saml/configs/saml-a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Internal IdP",
"entityId": "https://sso.acme.sa/saml/metadata",
"ssoUrl": "https://sso.acme.sa/saml/sso",
"sloUrl": "https://sso.acme.sa/saml/slo",
"idpCertificate": "-----BEGIN CERTIFICATE-----\nMIIBkTCB+...\n-----END CERTIFICATE-----",
"signRequests": true,
"wantAssertionsSigned": true,
"nameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"attributeMapping": {
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
"firstName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
"lastName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
},
"enabled": true
}'

Response 200 — Returns the updated SAML config object.

DELETE /api/v1/tenant/saml/configs/{id}

Permission: settings:write

Delete a SAML configuration. Active SAML sessions are not terminated immediately but will fail on the next assertion validation.

curl -X DELETE "https://api.ithbat.io/api/v1/tenant/saml/configs/saml-a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>"

Response 200

{
"success": true,
"data": null
}

POST /api/v1/tenant/saml/configs/import-metadata

Permission: settings:write

Parse a SAML metadata XML document and return the extracted configuration fields. This endpoint does not persist anything — use the returned values to pre-fill a POST /api/v1/tenant/saml/configs request.

Request Body

FieldTypeRequiredDescription
metadatastringYesRaw SAML metadata XML as a string
curl -X POST "https://api.ithbat.io/api/v1/tenant/saml/configs/import-metadata" \
-H "Authorization: Bearer <access_token>" \
-H "X-Tenant-ID: <tenant_id>" \
-H "Content-Type: application/json" \
-d '{
"metadata": "<?xml version=\"1.0\"?><EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"https://sso.acme.sa/saml/metadata\">...</EntityDescriptor>"
}'

Response 200

{
"success": true,
"data": {
"parsed": {
"entityId": "https://sso.acme.sa/saml/metadata",
"ssoUrl": "https://sso.acme.sa/saml/sso",
"sloUrl": "https://sso.acme.sa/saml/slo",
"idpCertificate": "-----BEGIN CERTIFICATE-----\nMIIBkTCB+...\n-----END CERTIFICATE-----",
"nameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
}
}
}