# Roam HQ SCIM 2.0 API

System for Cross-domain Identity Management (SCIM) v2 is a standard protocol for automating user provisioning.
Roam HQ supports SCIM 2.0 to allow enterprise identity providers (IdPs) like Okta to seamlessly create, update, deactivate, and synchronize user accounts in Roam.

**OpenAPI Spec:** [scim.json](https://developer.ro.am/scim.json)

## SCIM Base URL and Authentication

All SCIM API endpoints are served under the following base URL:
- **Base URL:** `https://api.ro.am/scim/v2`

Access to the SCIM API requires an App configured with the following scopes:
- `user:read`, `user:read.email` - for GET operations
- `user:write` - for write operations

In "Roam Administration > Technical Setup", click "Enable SCIM" to automatically generate an App and retrieve its Bearer token. 
Include the token in the `Authorization: Bearer <token>` header.
Discovery endpoints (`/ServiceProviderConfig`, `/Schemas`, `/ResourceTypes`) do **not** require authentication.

## Supported Endpoints

Roam exposes standard SCIM endpoints for Users (plus discovery endpoints). 
All endpoints accept and return data in the SCIM 2.0 JSON format (`application/scim+json`).

| Endpoint | Methods |
|----------|---------|
| `/ServiceProviderConfig` | `GET` |
| `/ResourceTypes` | `GET` |
| `/Schemas`, `/Schemas/{schemaId}` | `GET` |
| `/Users`, `/Users/{id}` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |
| `/Groups`, `/Groups/{id}` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |

> **Note:** PATCH support:
> - **Users**: `add`/`replace` of the same attributes PUT can update — `active`, `name.givenName`/`name.familyName`, `externalId`, and the Roam `role` extension — plus email changes when `userName` and the primary email are updated together (see below). Path-less operations (RFC 7644 §3.5.2.1), where the value is an object of attribute/value pairs, are supported — this is the form Okta uses to deactivate users. Operation names are case-insensitive. Attributes Roam does not store (e.g. `title`, `addresses`, `phoneNumbers`, enterprise-extension fields) are ignored rather than failing the request. `remove` is rejected for mapped attributes and ignored for unsupported ones.
> - **Groups**: `add`/`remove`/`replace` of `members`, and `replace` of `displayName` are supported.

## User Schema Highlights

- **Required:** Users minimally require a name and email: `userName` (email), `name.givenName`, `name.familyName`, `emails[0].value`
- **displayName:** Roam constructs the display name from givenName & familyName automatically. Any value you send for displayName will be ignored (no error). 
- **userName:** Must match the primary email address.
- **Email changes:** To change a user's email (e.g., a corporate rename), update `userName` and the primary email **together** to the same new address, via PUT or PATCH. Roam renames the account in place — chat history and group memberships are preserved. One-sided changes (only `userName`, or only `emails`) are rejected with a 400 error. If the change conflicts with existing data (e.g., the new address already belongs to an active user), the request returns 409 with the conflict reasons; contact Roam support to resolve.

- **Active:** `active: false` archives a user; `active: true` reactivates.
- **No Password Sync:** Roam does not use passwords and does not support SCIM password management. The password attribute in SCIM is ignored and will not be returned.
- **Role Extension:** Custom extension `urn:ro.am:params:scim:schemas:extension:roam:2.0:User.role` accepts `User` or `Admin`, defaulting to "User".
  This role controls access to Roam Administration.

## User Schema Reference

SCIM User Resource – Schema and Behavior

Roam’s SCIM User resource conforms to the SCIM core User schema (urn:ietf:params:scim:schemas:core:2.0:User) with a custom extension for user role. The following attributes are supported or have special behavior in Roam:

- id: (string, read-only) – The unique identifier for the user in Roam. 
  This is Roam’s internal Person ID, returned as a string in SCIM responses. 
  The id is generated by Roam and cannot be changed by the client.

- userName: (string, required) – The username for the account.
  Roam uses the email address as the userName.
  When creating a user, the userName field must be the user’s email address, and it must exactly match the primary email value provided.
  If userName does not match the email, the creation is rejected ("userName must match primary email" error).
  After creation, userName and the primary email can only change **together**: a PUT or PATCH that sets both to the same new address performs an email change (see “Changing a user’s email” below).
  Changing only one of the pair is rejected with a 400 error.
  (Uniqueness: Roam enforces that no two users in the same account have the same email/userName; creating a user with an email that already exists in your Roam account returns a SCIM uniqueness error 409.)

- name: (complex, required) – The structured name of the user. 
  Roam requires both givenName (first name) and familyName (last name) to be provided in the name object when creating or updating a user.
  If either is missing, the request is rejected ("name must contain givenName and familyName" error).
  Roam constructs the display name internally from these components. (Middle names or honorifics are not explicitly handled by SCIM in this implementation.)

- displayName: (string) – The full name of the user, typically for display purposes. 
  Roam ignores any displayName value sent in a SCIM request. 
  Instead, Roam will automatically set the display name to “givenName + familyName” (with a space) on the user’s account. 
  In SCIM GET responses, displayName is always populated with the user’s full name as stored in Roam. 
  (Any displayName provided on create/update is accepted but silently ignored.)

- emails: (array of complex) – The email addresses for the user. 
  Roam requires at least one email and uses the first entry as the primary email. 
  You should provide an array with a single object containing the user’s email, e.g. emails: `[{ "value": "alice@company.com", "primary": true, "type": "work" }]`. 
  The primary email’s value must equal the userName (Roam enforces this).
  Additional email entries (secondary emails) are ignored in the current implementation – only one email (the primary) is stored.
  The primary email can be changed after creation by updating it together with userName (see “Changing a user’s email” below).

- active: (boolean) – The active status of the user’s account. 
  If omitted on create, Roam assumes active: true (meaning the account is active/enabled). 
  Set active: false to indicate the user should be deactivated in Roam. 
  Deactivation behavior: If active is set to false in a PUT (or if the SCIM Delete operation is called), Roam will mark the user as Archived, which revokes their access but keeps their account record. 
  Setting active: true on an archived user will reactivate (un-archive) them.

- externalId: (string, optional) – An external identifier for the user, typically used to correlate the Roam user with the IdP’s user record. 
  Roam will store this value (if provided) as an external HR system ID for the user. 
  The externalId is returned in GET responses if it was set. 
  Roam does not enforce uniqueness or format on this field; it’s solely for the client’s reference. 
  (When a user is archived or reprovisioned, the externalId remains stored unless overwritten by a subsequent update.)

- password: (string, optional) – Not supported. 
  Roam does not use or sync user passwords via SCIM. 
  If a password attribute is included in a request, it will be ignored. 
  Roam does not return a password attribute in any response. 

- Roles (Custom Extension): Roam defines a custom SCIM extension to assign user roles (standard User vs Admin in Roam). 
  The extension URN is: `urn:ro.am:params:scim:schemas:extension:roam:2.0:User`.

  Under this extension, Roam supports a single attribute: role. 
  This is a string indicating the user’s role in Roam, with allowed values "User" or "Admin".
  In SCIM responses, each user’s data will include a section like:

  ```json
      "urn:ro.am:params:scim:schemas:extension:roam:2.0:User": {
          "role": "User"
      }
  ```

  By default, if you do not supply this extension on create, Roam will assign new users the role "User" (regular non-admin). 


Apart from the above, Roam’s SCIM user implementation ignores any other standard SCIM user attributes that are not listed.
Attributes such as middleName, honorificPrefix/Suffix, title, phoneNumbers, department, etc., if sent, will be ignored (and logged as unknown) -- they will not be stored or returned.
This applies to both PUT and PATCH, so an IdP profile push that bundles unsupported attributes still succeeds — the supported attributes are applied and the rest are ignored.
The enterprise-user extension schema (`urn:ietf:params:scim:schemas:extension:enterprise:2.0:User`) is registered so requests referencing it validate, but none of its attributes map to Roam fields — they are ignored.
The SCIM schemas list for a User response includes the core User schema URN, Roam’s extension URN (since the role sub-attribute is always present in responses, even if its value is "User" by default), and the enterprise-user extension URN.

## Changing a User’s Email

IdP-driven email changes (e.g. Okta or Entra profile renames) are applied when a PUT or PATCH updates `userName` and the primary email **together** to the same new address. Roam renames the account in place: the user keeps their chat history, group memberships, and account record, and any active sessions for the old and new address are signed out. Archived (deactivated) users can be renamed too, so a reactivate-and-rename push from the IdP works.

Error behavior:
- **400** – `userName` and the primary email do not match, or only one of the pair was changed (e.g. "email may not be updated without a matching userName; to change a member's email, set both to the new address").
- **409** – the change conflicts with existing data, e.g. the new address already belongs to an active user. The response `detail` lists the conflict reasons; these cases need an operator decision — contact Roam support.

## Group Membership Sync

Group member resolution is **best-effort**: on group create, PUT, and PATCH, member IDs that do not resolve to an active user in your account (unknown IDs, or archived/deactivated users) are skipped with a warning log instead of failing the request. The response body reflects the membership that was actually applied, and the IdP’s next sync re-adds a member once they exist. This means a stale member left in an IdP-side group (e.g. a deprovisioned employee) cannot wedge that group’s sync.

Archived users are neutral in group sync: they are never added to a group, and a sync never removes an archived user’s existing membership rows (membership is restored intact if they are reactivated).

## Okta Integration Steps (Summary)

1. In Okta, create a **SCIM 2.0 App Integration**.
2. Set Base URL to `https://api.ro.am/scim/v2`.
3. Provide a Roam API token with required scopes.
4. Test connection (Okta hits `/ServiceProviderConfig`).
5. Enable **Create Users**, **Update Users**, **Deactivate Users**.
6. Map attributes: The default mappings should work, but these are the ones required: 
    - First Name → `name.givenName`
    - Last Name → `name.familyName`
    - Email → `userName` / `emails[primary]`
7. (Optional) Map a custom attribute to the `role` extension for admin assignment.
8. Assign users to the app and monitor provisioning logs.

---

By following the above documentation and guidelines, you should be able to successfully integrate SCIM 2.0 provisioning for your Roam organization. 
This will automate user management and ensure your Roam user directory stays in sync with your identity provider. 
For further questions or support, reach out to Roam’s developer support.


## Base URL

```
https://api.ro.am/scim/v2
```

## Authentication

```
Authorization: Bearer YOUR_API_KEY
```

## All Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | [`/ServiceProviderConfig`](/docs/scim/03-metadata-service-provider-config) | Get Service Provider Config |
| GET | [`/ResourceTypes`](/docs/scim/03-metadata-resource-types) | Get Resource Types |
| GET | [`/Schemas`](/docs/scim/03-metadata-list-schemas) | List Schemas |
| GET | [`/Schemas/{schemaId}`](/docs/scim/03-metadata-get-schema) | Get Schema |
| GET | [`/Users`](/docs/scim/01-users-list) | List Users |
| POST | [`/Users`](/docs/scim/01-users-create) | Create User |
| GET | [`/Users/{id}`](/docs/scim/01-users-get) | Get User |
| PUT | [`/Users/{id}`](/docs/scim/01-users-replace) | Replace User |
| DELETE | [`/Users/{id}`](/docs/scim/01-users-delete) | Delete User |
| PATCH | [`/Users/{id}`](/docs/scim/01-users-patch) | Patch User |
| GET | [`/Groups`](/docs/scim/02-groups-list) | List Groups |
| POST | [`/Groups`](/docs/scim/02-groups-create) | Create Group |
| GET | [`/Groups/{id}`](/docs/scim/02-groups-get) | Get Group |
| PUT | [`/Groups/{id}`](/docs/scim/02-groups-replace) | Replace Group |
| PATCH | [`/Groups/{id}`](/docs/scim/02-groups-patch) | Patch Group |
| DELETE | [`/Groups/{id}`](/docs/scim/02-groups-delete) | Delete Group |

## Contact

- Email: developer@ro.am
- Website: https://developer.ro.am

---

*Machine-readable API documentation.*
*Full documentation: https://developer.ro.am/docs/scim*
