Ringover SCIM API
Ringover exposes a **SCIM 2.0** server (RFC 7643 / RFC 7644) so your identity provider (Okta, Microsoft Entra ID, OneLogin, …) can automatically provision, update and deprovision Ringover users and groups. ### Base URL | Region | SCIM Base URL | |--------|---------------| | Europe | `https://public-api.ringover.com/v2/scim/v2` | | US | `https://public-api-us.ringover.com/v2/scim/v2` | ### Authentication SCIM uses the **same API key** as the REST API, passed **raw** in the `Authorization` header (no `Bearer ` prefix). Unauthenticated requests receive a `401` carrying a `WWW-Authenticate: Bearer` challenge. ``` Authorization: ``` ### Content type Request and response bodies use the SCIM media type `application/scim+json`. `application/json` is also accepted on input. ### Permissions & Monitoring | Operation | Permission required | |-----------|---------------------| | Read (Get, List, `.search`) | `Users Read` | | Write (Create, Replace, Patch, Delete) | `Users Write` | Because SCIM operates on the **whole team** (an IdP manages every user), the API key must have **Monitoring ON**. Without it, listings are restricted to your own data and most provisioning operations are refused. ### Integration steps Follow these steps to connect a user manager — an identity provider such as Okta / Microsoft Entra ID, or a custom provisioning system — to Ringover: 1. **Create an API key** in the Ringover Dashboard (**Developer > API Keys**) with **Users Read + Write** and **Monitoring ON**. This single key authenticates every SCIM request. 2. **Point your SCIM connector** at the base URL `https://public-api.ringover.com/v2/scim/v2`. For the credentials, use an **HTTP Header Token** auth type and paste the API key **raw** as the token — it must reach Ringover as `Authorization: `, without a `Bearer ` prefix. 3. **Verify connectivity** by calling `GET /ServiceProviderConfig` — it returns the supported features and confirms authentication. Most IdPs expose this as a "Test connection" button. 4. **Map attributes**: at minimum, map your directory's login / UPN to `userName` **as an email address** (a valid email is mandatory to provision). Optionally map `name`, `title` and group membership. To assign a Ringover profile, map `roles` and send the **numeric profile id** (discover it via `GET /profiles`) as `roles[].value`. 5. **Provision** users with `POST /Users` and groups with `POST /Groups`; store the returned `id` to address the resource later. 6. **Push updates** with `PATCH /Users/{id}` (or `PUT`), and **deprovision** by sending `active: false` via `PATCH` (or `DELETE /Users/{id}`) — in Ringover this **deletes** the user. 7. **Reconcile** by `userName` (or `id`) — not by `externalId` (see limitations). The **end-to-end example** below walks through the full lifecycle as raw HTTP calls. ### Resources | Resource | Endpoint | Description | |----------|----------|-------------| | User | `/Users` | Ringover team members | | Group | `/Groups` | Ring groups | Discovery endpoints (`/ServiceProviderConfig`, `/ResourceTypes`, `/Schemas`) describe the server's capabilities. ### User attribute mapping | SCIM attribute | Ringover field | Notes | |----------------|----------------|-------| | `id` | user id | Raw Ringover user id (string). | | `userName` | email / login | Required. Used as the unique identifier. | | `name.givenName` / `name.familyName` | firstname / lastname | Writable. | | `displayName` | — | **Read-only**, derived from the name. A value sent by the client is ignored. | | `title` | job title | Writable. | | `emails` | account email | **Immutable** after onboarding. The primary email is the account identifier. | | `roles` | Ringover profile | Single profile. On input, `roles[].value` **must be the numeric profile id** (discover it via `GET /profiles`). On output, `value` is the id and `display` the profile name. | | `active` | lifecycle | `false` deprovisions (deletes) the user; `true` (or absent) is a no-op. | | `externalId` | — | **Not persisted.** Echoed in the create response only; never returned on reads. | ### Group attribute mapping | SCIM attribute | Ringover field | Notes | |----------------|----------------|-------| | `id` | group id | Raw Ringover group id (string). | | `displayName` | group name | Set at creation. **Rename is not supported** and is ignored on update. | | `members` | group users | `members[].value` is a user id. | ### Querying: filter, sort, pagination, projection - **Filter** (`filter`): a single comparison `attribute op "value"`, with `op` one of `eq`, `sw` (starts-with), `ew` (ends-with), `co` (contains). Filterable attributes: Users `userName`, `externalId`, `emails`; Groups `displayName`, `externalId`. Any other operator returns `400 invalidFilter`. - **Sort** (`sortBy`, `sortOrder`): case-insensitive, default ascending. An unsupported `sortBy` is ignored. - **Pagination** (`startIndex` 1-based, `count`): `count` is capped at **100** per page. - **Projection** (`attributes` / `excludedAttributes`): comma-separated, **top-level attributes only**; `schemas`, `id` and `meta` are always returned. ### PATCH `PATCH` follows RFC 7644 §3.5.2 with `add` / `remove` / `replace` operations. On groups, membership operations are reconciled into a single upstream update. ### End-to-end example (provisioning from a user manager) A typical provisioning lifecycle, shown as raw HTTP calls (replace `$TOKEN` with your API key). All requests target the EU base URL `https://public-api.ringover.com/v2/scim/v2`. **1. Verify the connection** ```bash curl -s https://public-api.ringover.com/v2/scim/v2/ServiceProviderConfig \ -H "Authorization: $TOKEN" ``` **2. Create a user** — this is a real onboarding: it sends an invitation and consumes a license and a phone number. ```bash curl -s -X POST https://public-api.ringover.com/v2/scim/v2/Users \ -H "Authorization: $TOKEN" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "ada@example.com", "name": { "givenName": "Ada", "familyName": "Lovelace" }, "emails": [{ "value": "ada@example.com", "primary": true }], "active": true }' ``` Response (`201 Created`) — store the `id` for the next calls: ```json { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "42", "userName": "ada@example.com", "active": true, "meta": { "resourceType": "User", "location": "/scim/v2/Users/42" } } ``` **3. Assign a role (Ringover profile)** — profile ids are discovered via the REST `GET /profiles` endpoint, then passed as `roles[].value`: ```bash # a) discover profile ids (REST API) curl -s https://public-api.ringover.com/v2/profiles -H "Authorization: $TOKEN" # b) assign profile id 12 to the user curl -s -X PATCH https://public-api.ringover.com/v2/scim/v2/Users/42 \ -H "Authorization: $TOKEN" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ { "op": "replace", "path": "roles", "value": [{ "value": "12", "primary": true }] } ] }' ``` **4. Create a group and add the user as a member** ```bash curl -s -X POST https://public-api.ringover.com/v2/scim/v2/Groups \ -H "Authorization: $TOKEN" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "Support", "members": [{ "value": "42" }] }' ``` **5. Update the user** (e.g. change the first name) ```bash curl -s -X PATCH https://public-api.ringover.com/v2/scim/v2/Users/42 \ -H "Authorization: $TOKEN" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ { "op": "replace", "path": "name.givenName", "value": "Augusta" } ] }' ``` **6. Deprovision the user** — set `active: false` (soft delete) or issue a `DELETE`: ```bash curl -s -X PATCH https://public-api.ringover.com/v2/scim/v2/Users/42 \ -H "Authorization: $TOKEN" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "replace", "path": "active", "value": false }] }' ``` > Reconcile users by `userName` or `id`; `externalId` is not returned on reads, so do not rely on it for matching. ### Behavior & limitations - **Provisioning is a real onboarding operation.** Creating a user sends an invitation and consumes a **license and a phone number** from your team. A valid email is required: provide a primary `emails` value or an email-formatted `userName`, otherwise the request is rejected with `400 invalidValue`. - **`emails` is immutable**: it cannot be changed, removed or replaced via `PUT`/`PATCH`. - **`displayName` is read-only** and derived from the name. - **Group rename is not supported**: `displayName` is fixed after creation; updates only reconcile members. - **`active: false` deletes the user** (soft delete — the only deprovisioning lever). **`active: true` is a no-op** and there is **no reactivation** via SCIM. - **`roles[].value` must be a numeric profile id.** A non-numeric value (e.g. a role name) is **silently ignored** (no error), leaving the user with no profile assigned. Discover profile ids via `GET /profiles`. - **`externalId` is not persisted**: it is returned only in the create response, never on reads. Consequently `filter=externalId eq "..."` never matches, and reconciliation by `externalId` does not work — reconcile by `userName` or `id`. - **Multi-attribute `PUT`/`PATCH` on a user is not atomic**: each field is applied upstream independently, so a later failure can leave earlier fields already changed. - **`meta.created` / `meta.lastModified` are currently omitted** (they will be populated once available upstream). - **`Bulk` and custom resource types are not supported.**