> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.rhombus.community/llms.txt
> Use this file to discover all available pages before exploring further.

# SAML SSO & SCIM Provisioning

> Configure SAML single sign-on and SCIM user provisioning for Rhombus via the API — integrate Okta, Azure AD, Google Workspace, OneLogin, and other IdPs.

## Overview

Rhombus supports **SAML 2.0** so your employees can sign in to the Rhombus Console with their existing corporate identity (Okta, Azure AD / Microsoft Entra, Google Workspace, OneLogin, and any SAML-compliant IdP), and **SCIM 2.0** so user lifecycle changes in your IdP propagate to Rhombus automatically.

This guide covers the **API** surface for configuring both. Use it when you need to:

* **Manage Rhombus as infrastructure-as-code** (Terraform, Pulumi, in-house CI scripts)
* **Rotate IdP signing certificates** on a scheduled cadence
* **Onboard many Rhombus orgs** from a partner or MSP control plane
* **Audit or diff** identity configuration across environments
* **Stand up a new tenant** end-to-end without clicking through the Console

If you only need to configure SSO once for a single organization, the [Rhombus Console](https://console.rhombussystems.com/) has a UI for everything in this guide — but everything the UI can do is exposed via the API.

<Note>
  **What this guide isn't.** If you're a third-party developer building an application that authenticates Rhombus users (a "Sign in with Rhombus" flow), you want [Sign in with Rhombus](/oauth-authentication) instead. That's OAuth 2.0 with Rhombus as the Identity Provider — a completely separate surface from the workforce-SSO configuration covered here.
</Note>

## Architecture at a glance

Two independent flows share your IdP but don't otherwise depend on each other:

```mermaid theme={null}
flowchart LR
    subgraph IdP["Your IdP (Okta, Azure AD, Google, etc.)"]
        Users[Users & Groups]
        Meta[SAML Metadata XML]
    end

    subgraph Rhombus["Rhombus"]
        Console[Rhombus Console<br/>Sign-in page]
        API[api2.rhombussystems.com]
        SCIM[SCIM endpoint<br/>scimEndpointUrl from getScimDisplayInfo]
    end

    Users -- "SAML assertion on login" --> Console
    Meta -. "uploaded via updateSAMLSettingsV2" .-> API
    Users -- "SCIM create/update/delete" --> SCIM
    API -- "exposes scimEndpointUrl" --> IdP
```

* **SAML SSO** authenticates users at sign-in time. Your IdP posts a SAML assertion to Rhombus; Rhombus validates it against the IdP metadata XML you uploaded.
* **SCIM** propagates user lifecycle events (create, update, deactivate) from your IdP to Rhombus continuously, without requiring users to sign in first.

You can run either one alone, but most deployments run both: **SAML for sign-in, SCIM for provisioning**.

## Before you begin

<Info>
  Before you start, make sure you have:

  * A **Rhombus API key** with organization-admin permissions (generated in the [Rhombus Console](https://console.rhombussystems.com/) under **Settings → API Management**)
  * An **identity provider** that supports SAML 2.0 and (for provisioning) SCIM 2.0
  * The **IdP metadata XML** for your SAML application, either as a file or URL
  * A recovery user account with a password that **bypasses SAML** — see [break-glass access](#break-glass-access) before you enable SSO
</Info>

***

# Part 1: SAML SSO

Two endpoints cover the entire SAML configuration lifecycle:

| Endpoint                             | Purpose                                         |
| ------------------------------------ | ----------------------------------------------- |
| `POST /api/org/getSAMLSettingsV2`    | Read current SAML settings for the organization |
| `POST /api/org/updateSAMLSettingsV2` | Replace SAML settings                           |

<Note>
  `getSAMLSettings` (v1, no `V2` suffix) and `updateSAMLSettings` (v1) are **deprecated**. Use the V2 endpoints shown in this guide.
</Note>

## Read current SAML settings

Fetch the current configuration so you can diff against what you intend to push, or confirm a previous update landed.

<CodeGroup>
  ```python Python theme={null}
  import requests

  headers = {
      "x-auth-scheme": "api-token",
      "x-auth-apikey": "YOUR_API_KEY",
      "Content-Type": "application/json",
  }

  response = requests.post(
      "https://api2.rhombussystems.com/api/org/getSAMLSettingsV2",
      headers=headers,
      json={},
  )

  for setting in response.json().get("samlSettings", []):
      print(f"team={setting.get('teamName')} enabled={setting.get('enabled')} "
            f"jit={setting.get('justInTimeAccountProvisioningEnabled')}")
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api2.rhombussystems.com/api/org/getSAMLSettingsV2",
    {
      method: "POST",
      headers: {
        "x-auth-scheme": "api-token",
        "x-auth-apikey": "YOUR_API_KEY",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({}),
    }
  );

  const { samlSettings = [] } = await response.json();
  for (const s of samlSettings) {
    console.log(`team=${s.teamName} enabled=${s.enabled} jit=${s.justInTimeAccountProvisioningEnabled}`);
  }
  ```

  ```go Go theme={null}
  req, _ := http.NewRequest("POST",
      "https://api2.rhombussystems.com/api/org/getSAMLSettingsV2",
      strings.NewReader("{}"))
  req.Header.Set("x-auth-scheme", "api-token")
  req.Header.Set("x-auth-apikey", "YOUR_API_KEY")
  req.Header.Set("Content-Type", "application/json")

  resp, _ := http.DefaultClient.Do(req)
  defer resp.Body.Close()

  var result struct {
      SamlSettings []struct {
          TeamName                              string `json:"teamName"`
          Enabled                               bool   `json:"enabled"`
          JustInTimeAccountProvisioningEnabled  bool   `json:"justInTimeAccountProvisioningEnabled"`
      } `json:"samlSettings"`
  }
  json.NewDecoder(resp.Body).Decode(&result)
  ```
</CodeGroup>

The response contains a `samlSettings` array. Most orgs have one entry — a second entry exists only for deployments that span both `rhombus.com` and `rhombussystems.com` domains.

## Configure SAML

`POST /api/org/updateSAMLSettingsV2` replaces the organization's SAML configuration. Include the entries you want to exist after the call — fields omitted inside an entry revert to defaults.

### The `OrgSamlSettingsType` fields

| Field                                  | Type    | Description                                                                                                                      |
| -------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `enabled`                              | boolean | Turn SAML sign-in on or off. Set to `false` to disable SSO without losing your metadata.                                         |
| `idpMetaDataXml`                       | string  | The raw SAML metadata XML from your IdP (entity descriptor, signing cert, SSO URLs).                                             |
| `justInTimeAccountProvisioningEnabled` | boolean | If `true`, a Rhombus user is auto-created the first time someone signs in via SAML. See [JIT vs SCIM](#jit-vs-scim) below.       |
| `enabledForRhombusKey`                 | boolean | Also require SAML for the Rhombus Key mobile access app.                                                                         |
| `addUsersOnRoleMismatch`               | boolean | If `true`, sign-in succeeds even when the IdP-asserted role doesn't match a Rhombus role; the user is added with a default role. |
| `teamName`                             | string  | Display label used in the Console.                                                                                               |
| `domain`                               | enum    | `RHOMBUS_COM` or `RHOMBUS_SYSTEMS_COM`. Most customers use `RHOMBUS_COM`.                                                        |
| `rhombusKeyAppSettings`                | object  | Per-app toggles for the Rhombus Key mobile app (remote unlock, SAML bypass for mobile, etc.).                                    |

### Upload IdP metadata

<CodeGroup>
  ```python Python theme={null}
  import requests

  # Read your IdP's federation metadata XML
  with open("idp-metadata.xml", "r") as f:
      idp_metadata_xml = f.read()

  headers = {
      "x-auth-scheme": "api-token",
      "x-auth-apikey": "YOUR_API_KEY",
      "Content-Type": "application/json",
  }

  body = {
      "samlSettings": [
          {
              "enabled": True,
              "idpMetaDataXml": idp_metadata_xml,
              "justInTimeAccountProvisioningEnabled": True,
              "enabledForRhombusKey": True,
              "addUsersOnRoleMismatch": False,
              "teamName": "Acme Corp",
              "domain": "RHOMBUS_COM",
          }
      ]
  }

  response = requests.post(
      "https://api2.rhombussystems.com/api/org/updateSAMLSettingsV2",
      headers=headers,
      json=body,
  )
  response.raise_for_status()
  print("SAML configuration updated.")
  ```

  ```javascript JavaScript theme={null}
  import fs from "node:fs/promises";

  const idpMetadataXml = await fs.readFile("idp-metadata.xml", "utf8");

  const response = await fetch(
    "https://api2.rhombussystems.com/api/org/updateSAMLSettingsV2",
    {
      method: "POST",
      headers: {
        "x-auth-scheme": "api-token",
        "x-auth-apikey": "YOUR_API_KEY",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        samlSettings: [
          {
            enabled: true,
            idpMetaDataXml: idpMetadataXml,
            justInTimeAccountProvisioningEnabled: true,
            enabledForRhombusKey: true,
            addUsersOnRoleMismatch: false,
            teamName: "Acme Corp",
            domain: "RHOMBUS_COM",
          },
        ],
      }),
    }
  );

  if (!response.ok) throw new Error(`Update failed: ${await response.text()}`);
  console.log("SAML configuration updated.");
  ```

  ```go Go theme={null}
  xmlBytes, _ := os.ReadFile("idp-metadata.xml")

  body, _ := json.Marshal(map[string]any{
      "samlSettings": []map[string]any{
          {
              "enabled":                              true,
              "idpMetaDataXml":                       string(xmlBytes),
              "justInTimeAccountProvisioningEnabled": true,
              "enabledForRhombusKey":                 true,
              "addUsersOnRoleMismatch":               false,
              "teamName":                             "Acme Corp",
              "domain":                               "RHOMBUS_COM",
          },
      },
  })

  req, _ := http.NewRequest("POST",
      "https://api2.rhombussystems.com/api/org/updateSAMLSettingsV2",
      bytes.NewReader(body))
  req.Header.Set("x-auth-scheme", "api-token")
  req.Header.Set("x-auth-apikey", "YOUR_API_KEY")
  req.Header.Set("Content-Type", "application/json")

  resp, err := http.DefaultClient.Do(req)
  if err != nil || resp.StatusCode != 200 {
      // handle error
  }
  ```
</CodeGroup>

<Warning>
  `updateSAMLSettingsV2` is a **replacement** operation. To change one field (say, `teamName`), read the current settings first, modify the entry you want, and send the full array back. Omitting an entry that previously existed removes it.
</Warning>

## JIT vs SCIM

You have two options for getting users into Rhombus:

| Option                                                 | What it does                                                                     | When to choose it                                                                                         |
| ------------------------------------------------------ | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| **JIT** (`justInTimeAccountProvisioningEnabled: true`) | A Rhombus user record is created the first time a person signs in via SAML.      | Small teams, low churn, SCIM unavailable on your IdP tier.                                                |
| **SCIM** ([Part 2](#part-2-scim-provisioning))         | Your IdP pushes create, update, and deactivate events to Rhombus as they happen. | Larger orgs, compliance-driven deprovisioning requirements, role changes that must propagate immediately. |

You can enable both — JIT handles any user who signs in before SCIM syncs them, and SCIM keeps the directory consistent thereafter. Most production deployments run both.

## Rotating IdP metadata

IdP signing certificates rotate. Build a scheduled job that pulls fresh metadata from your IdP and pushes it to Rhombus — Rhombus keeps accepting assertions signed with the old cert until you replace the metadata.

```python Python theme={null}
import requests

def rotate_saml_metadata(api_key: str, new_metadata_xml: str) -> None:
    """Fetch current SAML settings, swap in new IdP metadata, push back."""
    headers = {
        "x-auth-scheme": "api-token",
        "x-auth-apikey": api_key,
        "Content-Type": "application/json",
    }

    # 1. Read current settings
    current = requests.post(
        "https://api2.rhombussystems.com/api/org/getSAMLSettingsV2",
        headers=headers,
        json={},
    ).json()

    settings = current.get("samlSettings") or []
    if not settings:
        raise RuntimeError("No existing SAML settings to rotate.")

    # 2. Replace the metadata XML on each entry; leave other fields untouched
    for entry in settings:
        entry["idpMetaDataXml"] = new_metadata_xml

    # 3. Push back
    response = requests.post(
        "https://api2.rhombussystems.com/api/org/updateSAMLSettingsV2",
        headers=headers,
        json={"samlSettings": settings},
    )
    response.raise_for_status()
```

<Tip>
  Schedule the rotation well before your IdP cert actually expires. Verify a real sign-in immediately after rotation, before closing the maintenance window — a malformed XML update can prevent logins until you revert.
</Tip>

## Break-glass access

**Before you enable SAML, make sure at least one admin user has a Rhombus-native password and can bypass SAML.** Otherwise a misconfiguration locks everyone out and the only recovery path is Rhombus Support.

Every Rhombus user record has a `bypassSaml` boolean. Keep one or two admin accounts with `bypassSaml: true` as a recovery path. These accounts should:

* Use strong, unique passwords stored in your secrets manager
* Have MFA enabled
* Be audited regularly — treat them like root credentials

<Warning>
  Never disable SAML **and** remove all break-glass accounts in the same change. Test your recovery path at least once per quarter.
</Warning>

***

# Part 2: SCIM provisioning

SCIM (System for Cross-domain Identity Management) lets your IdP push user lifecycle events directly to Rhombus — no sign-in required.

Five endpoints cover SCIM end-to-end:

| Endpoint                                 | Purpose                                                   |
| ---------------------------------------- | --------------------------------------------------------- |
| `POST /api/org/getScimDisplayInfo`       | Get the SCIM endpoint URLs to hand to your IdP            |
| `POST /api/org/setupSCIMAccessForOrg`    | First-time SCIM setup; returns a bearer token             |
| `POST /api/org/findSCIMSettingsForOrg`   | Read current SCIM configuration                           |
| `POST /api/org/updateSCIMSettingsForOrg` | Change SCIM options (welcome emails, role behavior, etc.) |
| `POST /api/org/revokeSCIMAccessForOrg`   | Invalidate the current SCIM bearer token                  |

## Get the SCIM endpoint URLs

Your IdP needs two pieces of information to connect: the **endpoint URL** and a **bearer token**. The endpoint URL comes from `getScimDisplayInfo`:

<CodeGroup>
  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api2.rhombussystems.com/api/org/getScimDisplayInfo",
      headers={
          "x-auth-scheme": "api-token",
          "x-auth-apikey": "YOUR_API_KEY",
          "Content-Type": "application/json",
      },
      json={},
  )

  info = response.json()
  print("Standard SCIM endpoint:", info["scimEndpointUrl"])
  print("Azure AD SCIM endpoint:", info["azureScimEndpointUrl"])
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api2.rhombussystems.com/api/org/getScimDisplayInfo",
    {
      method: "POST",
      headers: {
        "x-auth-scheme": "api-token",
        "x-auth-apikey": "YOUR_API_KEY",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({}),
    }
  );

  const info = await response.json();
  console.log("Standard SCIM endpoint:", info.scimEndpointUrl);
  console.log("Azure AD SCIM endpoint:", info.azureScimEndpointUrl);
  ```

  ```go Go theme={null}
  req, _ := http.NewRequest("POST",
      "https://api2.rhombussystems.com/api/org/getScimDisplayInfo",
      strings.NewReader("{}"))
  req.Header.Set("x-auth-scheme", "api-token")
  req.Header.Set("x-auth-apikey", "YOUR_API_KEY")
  req.Header.Set("Content-Type", "application/json")

  resp, _ := http.DefaultClient.Do(req)
  defer resp.Body.Close()

  var info struct {
      ScimEndpointUrl      string `json:"scimEndpointUrl"`
      AzureScimEndpointUrl string `json:"azureScimEndpointUrl"`
  }
  json.NewDecoder(resp.Body).Decode(&info)
  ```
</CodeGroup>

Most IdPs use `scimEndpointUrl`. Azure AD / Entra requires the Azure-specific variant due to Microsoft-specific schema quirks.

## Initial SCIM setup

The first call provisions a bearer token. **This token is shown once and cannot be retrieved later** — capture it immediately into your secrets manager.

<CodeGroup>
  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api2.rhombussystems.com/api/org/setupSCIMAccessForOrg",
      headers={
          "x-auth-scheme": "api-token",
          "x-auth-apikey": "YOUR_API_KEY",
          "Content-Type": "application/json",
      },
      json={
          "sendWelcomeEmailToNewUsers": True,
          "sendWelcomeEmailToNewRhombusKeyUsers": True,
          "addUsersOnRoleMismatch": False,
      },
  )

  data = response.json()
  if data.get("scimAccessAlreadySetupFailure"):
      raise RuntimeError("SCIM is already set up — revoke first or use update.")

  scim_bearer_token = data["token"]
  # Store scim_bearer_token in your secrets manager immediately.
  # Paste it into your IdP's SCIM provisioning settings as the bearer token.
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api2.rhombussystems.com/api/org/setupSCIMAccessForOrg",
    {
      method: "POST",
      headers: {
        "x-auth-scheme": "api-token",
        "x-auth-apikey": "YOUR_API_KEY",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        sendWelcomeEmailToNewUsers: true,
        sendWelcomeEmailToNewRhombusKeyUsers: true,
        addUsersOnRoleMismatch: false,
      }),
    }
  );

  const data = await response.json();
  if (data.scimAccessAlreadySetupFailure) {
    throw new Error("SCIM is already set up — revoke first or use update.");
  }

  const scimBearerToken = data.token;
  // Store scimBearerToken in your secrets manager immediately.
  ```

  ```go Go theme={null}
  body, _ := json.Marshal(map[string]any{
      "sendWelcomeEmailToNewUsers":           true,
      "sendWelcomeEmailToNewRhombusKeyUsers": true,
      "addUsersOnRoleMismatch":               false,
  })

  req, _ := http.NewRequest("POST",
      "https://api2.rhombussystems.com/api/org/setupSCIMAccessForOrg",
      bytes.NewReader(body))
  req.Header.Set("x-auth-scheme", "api-token")
  req.Header.Set("x-auth-apikey", "YOUR_API_KEY")
  req.Header.Set("Content-Type", "application/json")

  resp, _ := http.DefaultClient.Do(req)
  defer resp.Body.Close()

  var data struct {
      Token                        string `json:"token"`
      ScimAccessAlreadySetupFailure bool   `json:"scimAccessAlreadySetupFailure"`
  }
  json.NewDecoder(resp.Body).Decode(&data)
  // Store data.Token in your secrets manager immediately.
  ```
</CodeGroup>

<Warning>
  The SCIM bearer token is shown **once**. Losing it means revoking the current access and setting up again — and your IdP loses sync until its token is updated.
</Warning>

### Setup options

| Field                                  | Type    | Description                                                          |
| -------------------------------------- | ------- | -------------------------------------------------------------------- |
| `sendWelcomeEmailToNewUsers`           | boolean | Email new users when SCIM creates their Rhombus account.             |
| `sendWelcomeEmailToNewRhombusKeyUsers` | boolean | Same, but for users added to the Rhombus Key mobile access app.      |
| `addUsersOnRoleMismatch`               | boolean | Create the user anyway if the IdP-asserted role doesn't map cleanly. |
| `rhombusKeyAppSettings`                | object  | Per-app toggles for Rhombus Key provisioning.                        |

## Read current SCIM settings

`findSCIMSettingsForOrg` returns the full `SCIMSettingsType` record, including the **role format** setting, which matters for IdP compatibility:

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(
      "https://api2.rhombussystems.com/api/org/findSCIMSettingsForOrg",
      headers=headers,
      json={},
  )

  settings = response.json().get("scimSettings", {})
  print("rolesFormat:", settings.get("rolesFormat"))  # LIST_OF_STRINGS or LIST_OF_MULTI_VALUED_ATTRIBUTES
  print("welcome emails:", settings.get("sendWelcomeEmailToNewUsers"))
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api2.rhombussystems.com/api/org/findSCIMSettingsForOrg",
    { method: "POST", headers, body: "{}" }
  );
  const { scimSettings = {} } = await response.json();
  console.log("rolesFormat:", scimSettings.rolesFormat);
  ```

  ```go Go theme={null}
  req, _ := http.NewRequest("POST",
      "https://api2.rhombussystems.com/api/org/findSCIMSettingsForOrg",
      strings.NewReader("{}"))
  req.Header.Set("x-auth-scheme", "api-token")
  req.Header.Set("x-auth-apikey", "YOUR_API_KEY")
  req.Header.Set("Content-Type", "application/json")
  // ... decode response.scimSettings
  ```
</CodeGroup>

### Role format compatibility

Rhombus accepts SCIM role assertions in two shapes via `rolesFormat`:

| Value                             | Shape                                                | Typical IdP            |
| --------------------------------- | ---------------------------------------------------- | ---------------------- |
| `LIST_OF_STRINGS`                 | `"roles": ["admin", "viewer"]`                       | Okta, OneLogin, Google |
| `LIST_OF_MULTI_VALUED_ATTRIBUTES` | `"roles": [{"value": "admin"}, {"value": "viewer"}]` | Azure AD / Entra       |

If users are being created but their roles aren't assigned correctly, verify this setting against what your IdP actually sends.

## Update SCIM settings

`updateSCIMSettingsForOrg` changes SCIM behavior without re-issuing the bearer token:

```python Python theme={null}
response = requests.post(
    "https://api2.rhombussystems.com/api/org/updateSCIMSettingsForOrg",
    headers=headers,
    json={
        "sendWelcomeEmailToNewUsers": False,
        "sendWelcomeEmailToNewRhombusKeyUsers": True,
        "addUsersOnRoleMismatch": True,
    },
)
response.raise_for_status()
```

## Rotate or revoke the SCIM bearer token

To rotate: **revoke, then set up again**. Your IdP must be updated with the new token in the same maintenance window or provisioning will fail.

<CodeGroup>
  ```python Python theme={null}
  # 1. Revoke current access
  requests.post(
      "https://api2.rhombussystems.com/api/org/revokeSCIMAccessForOrg",
      headers=headers,
      json={},
  ).raise_for_status()

  # 2. Issue a new token with the same options as before
  new = requests.post(
      "https://api2.rhombussystems.com/api/org/setupSCIMAccessForOrg",
      headers=headers,
      json={
          "sendWelcomeEmailToNewUsers": True,
          "sendWelcomeEmailToNewRhombusKeyUsers": True,
          "addUsersOnRoleMismatch": False,
      },
  ).json()

  new_token = new["token"]
  # 3. Push new_token into your IdP's SCIM bearer-token field.
  ```

  ```javascript JavaScript theme={null}
  // 1. Revoke
  await fetch("https://api2.rhombussystems.com/api/org/revokeSCIMAccessForOrg",
    { method: "POST", headers, body: "{}" });

  // 2. Re-issue
  const setupResp = await fetch(
    "https://api2.rhombussystems.com/api/org/setupSCIMAccessForOrg",
    {
      method: "POST",
      headers,
      body: JSON.stringify({
        sendWelcomeEmailToNewUsers: true,
        sendWelcomeEmailToNewRhombusKeyUsers: true,
        addUsersOnRoleMismatch: false,
      }),
    }
  );
  const { token: newToken } = await setupResp.json();
  // 3. Update IdP with newToken.
  ```

  ```go Go theme={null}
  revokeReq, _ := http.NewRequest("POST",
      "https://api2.rhombussystems.com/api/org/revokeSCIMAccessForOrg",
      strings.NewReader("{}"))
  // set headers, execute ...

  // Then setup again to get a new token (see earlier example).
  ```
</CodeGroup>

<Warning>
  Revoking SCIM immediately stops your IdP from syncing users. Existing Rhombus users are unaffected — they remain active and can still sign in via SAML — but any IdP changes (new hires, deprovisioned users) won't propagate until SCIM is restored.
</Warning>

***

## IdP-specific setup notes

The API calls above are the same across every IdP. What differs is where to paste the Rhombus values in your IdP's console. These notes capture what customers most often need.

<Note>
  IdP consoles change their UI frequently. Treat these as starting points and fall back to the IdP's own documentation if a menu has moved.
</Note>

<AccordionGroup>
  <Accordion title="Okta">
    **SAML.** Create a new **SAML 2.0** application. For the SSO URL and Audience URI, use the values from your organization's Rhombus SAML ACS (visible in the Console under **Settings → SSO / SAML**). Download the Okta IdP metadata XML from the application's **Sign On** tab and pass it to `updateSAMLSettingsV2` as `idpMetaDataXml`.

    **SCIM.** In the Okta app's **Provisioning** tab, select **SCIM 2.0**. Set the **SCIM connector base URL** to the `scimEndpointUrl` from `getScimDisplayInfo`. Set **Authentication Mode** to **HTTP Header**, with `Authorization: Bearer <scim-token>`. Set `rolesFormat` to `LIST_OF_STRINGS`.
  </Accordion>

  <Accordion title="Azure AD / Microsoft Entra">
    **SAML.** Create a new **Enterprise Application** → **Non-gallery**. On **Single sign-on**, choose SAML and import Rhombus's SP metadata. Download the **Federation Metadata XML** from Azure and pass it to `updateSAMLSettingsV2`.

    **SCIM.** On **Provisioning**, choose **Automatic**. Use the **`azureScimEndpointUrl`** (not the standard endpoint) as the Tenant URL, and paste the bearer token from `setupSCIMAccessForOrg` as the Secret Token. Set `rolesFormat` to **`LIST_OF_MULTI_VALUED_ATTRIBUTES`** — Azure sends role assertions in this shape.
  </Accordion>

  <Accordion title="Google Workspace">
    **SAML.** From the Google Admin Console, add a custom SAML app targeting Rhombus. Download the Google IdP metadata and pass it to `updateSAMLSettingsV2`.

    **SCIM.** Google Workspace supports SCIM for some apps via **Automated user provisioning**. Use the `scimEndpointUrl` with bearer token authentication. Set `rolesFormat` to `LIST_OF_STRINGS`.
  </Accordion>

  <Accordion title="OneLogin">
    **SAML.** Create a new SAML 2.0 connector; configure the ACS URL and Entity ID from Rhombus's SP metadata. Download the OneLogin IdP metadata XML and pass it to `updateSAMLSettingsV2`.

    **SCIM.** OneLogin's SCIM v2 provisioning takes the standard `scimEndpointUrl` and a bearer token. Set `rolesFormat` to `LIST_OF_STRINGS`.
  </Accordion>
</AccordionGroup>

***

## Auditing SSO events

Every SAML login attempt — success or failure — is written to the Rhombus audit log, as are changes to SAML configuration itself. Pull these events via `POST /api/report/getAuditFeed`.

Audit event types relevant to SSO:

| Event type                  | Fires when                                              |
| --------------------------- | ------------------------------------------------------- |
| `SAML_LOGIN_WEB`            | A user successfully signs in via SAML on the Console    |
| `SAML_LOGIN_FAILURE_WEB`    | A SAML sign-in failed on the Console                    |
| `SAML_LOGIN_MOBILE`         | A user signed in via SAML on a Rhombus mobile app       |
| `SAML_LOGIN_FAILURE_MOBILE` | A SAML sign-in failed on a Rhombus mobile app           |
| `RHOMBUS_KEY_SAML_LOGIN`    | A user signed in via SAML on the Rhombus Key mobile app |
| `UPDATE_INTEGRATION_SAML`   | SAML configuration itself was changed                   |

<Tip>
  Stream `UPDATE_INTEGRATION_SAML` events into your SIEM to detect unauthorized changes to SSO configuration, and alert on any `SAML_LOGIN_FAILURE_*` spike to catch broken rotations fast.
</Tip>

## Troubleshooting

<AccordionGroup>
  <Accordion title="`updateSAMLSettingsV2` succeeds but users can't sign in">
    Most often an issue with the metadata XML itself rather than the API call.

    * **Verify the XML** is well-formed and contains a current signing certificate — an expired cert in the metadata blocks every assertion.
    * **Verify the entity ID** in the metadata matches what Rhombus expects (visible in the Console under **Settings → SSO**).
    * **Verify JIT is enabled** if this is the first sign-in for these users and SCIM hasn't synced them yet — otherwise Rhombus has no user to map the assertion to.
    * **Check `UPDATE_INTEGRATION_SAML` audit events** to confirm the change landed as intended.
  </Accordion>

  <Accordion title="SCIM users are created but their roles are wrong">
    This is almost always `rolesFormat` mismatch. Call `findSCIMSettingsForOrg`, verify the value, and cross-reference against the [role format compatibility table](#role-format-compatibility). Azure AD needs `LIST_OF_MULTI_VALUED_ATTRIBUTES`; most others need `LIST_OF_STRINGS`.
  </Accordion>

  <Accordion title="SCIM setup returns `scimAccessAlreadySetupFailure: true`">
    SCIM is already configured. Either (a) call `revokeSCIMAccessForOrg` and re-run setup to get a fresh token, or (b) call `updateSCIMSettingsForOrg` to modify options without touching the token.
  </Accordion>

  <Accordion title="SCIM provisioning stopped working after rotation">
    The IdP is still using the old bearer token. After `revokeSCIMAccessForOrg` + fresh `setupSCIMAccessForOrg`, the new token **must** be pasted into the IdP's provisioning config. Do both in one maintenance window to avoid a sync gap.
  </Accordion>

  <Accordion title="Locked out of SAML — can't sign in, no API key handy">
    Use a break-glass account with `bypassSaml: true` and a Rhombus-native password. If none exists, contact Rhombus Support. This is why [break-glass access](#break-glass-access) is a prerequisite, not a nice-to-have.
  </Accordion>

  <Accordion title="JIT creates users with no role">
    Set `addUsersOnRoleMismatch: false` to reject users whose IdP role doesn't map, forcing the IdP operator to fix the assertion. Set it to `true` if you prefer to create the user and assign a role in Rhombus after the fact. Pick deliberately — the two behaviors are mutually exclusive.
  </Accordion>
</AccordionGroup>

## Next steps

<Columns cols={2}>
  <Card title="Sign in with Rhombus" icon="key" href="/oauth-authentication">
    Build third-party apps that authenticate Rhombus users (OAuth 2.0, separate from workforce SSO)
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/overview">
    Full schema for every endpoint used in this guide (Org tag)
  </Card>

  <Card title="Rate Limits" icon="gauge" href="/rate-limits">
    Understand request limits for scripted rotations and audits
  </Card>

  <Card title="Developer Community" icon="users" href="https://rhombus.community">
    Ask SSO questions and share IdP-specific setup tips
  </Card>
</Columns>
