> For the complete documentation index, see [llms.txt](https://docs.interlynk.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.interlynk.io/api/user-management/user-management.md).

# Manage Users and Roles

Interlynk controls access with role-based access control (RBAC). Every user in an organization holds one role, and a role is a named set of permissions. The API lets you invite users, change the role a user holds, remove users, and define custom roles.

{% hint style="success" %}
**Prefer SSO for managing access.** If your identity provider supports SAML (Okta, Microsoft Entra ID, Google Workspace, and others), connect it once and users are provisioned automatically on first login with a default role. You stop maintaining the member list by hand, access follows your directory, and deprovisioning happens where it should. See [Single sign-on](#single-sign-on) below. Use the invite flow for people outside your IdP or for early setup.
{% endhint %}

## How access works

| Concept        | What it is                                                      |
| -------------- | --------------------------------------------------------------- |
| **Role**       | A named set of permissions, scoped to one organization.         |
| **Permission** | A single capability, for example `invite_users` or `view_sbom`. |
| **Member**     | A user attached to an organization with exactly one role.       |

Three system roles exist in every organization: **Admin** (all permissions), **Operator**, and **Viewer** (read-only). Enterprise organizations can also define custom roles. The actions below each require a permission, so the token you use must belong to a user whose role grants it.

| Action                  | Mutation                 | Permission required                     |
| ----------------------- | ------------------------ | --------------------------------------- |
| Add a user              | `organizationUserInvite` | `invite_users`                          |
| Change a user's role    | `organizationUserUpdate` | `edit_user_role`                        |
| Remove a user           | `organizationUserRemove` | `delete_user`                           |
| Create or change a role | `organizationRole*`      | `edit_user_role`, `update_organization` |
| Configure SSO           | `samlConfig*`            | `edit_connections`                      |

A token without the permission gets `You are not authorized to perform this action on User`. The Admin role holds every permission.

## Find role and user IDs

The user-management mutations take a `userId` and an `organizationRoleId`. Read both from the `organization` query.

### List roles

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "query { organization { organizationRoles { id name isAdmin isSystem } } }"}'
```

```json
{
  "data": {
    "organization": {
      "organizationRoles": [
        { "id": "29470b2d-...", "name": "Admin", "isAdmin": true, "isSystem": true },
        { "id": "1d6d5eff-...", "name": "Operator", "isAdmin": false, "isSystem": true },
        { "id": "784f2a4f-...", "name": "Viewer", "isAdmin": false, "isSystem": true }
      ]
    }
  }
}
```

### List members

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "query { organization { users(first: 25) { nodes { id name email invitationStatus role { name } } } } }"}'
```

`invitationStatus` is `invited` for a pending invitation, `accepted` once the user joins, `declined`, or `pending_registration`.

## Add a user

`organizationUserInvite` invites a user by email and assigns a role. Pass the `organizationRoleId` from the roles list. The user gets an email and shows as `invited` until they accept.

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation Invite($email: String!, $roleId: Uuid!) { organizationUserInvite(input: { email: $email, organizationRoleId: $roleId }) { user { id email invitationStatus role { name } } errors } }",
    "variables": {
      "email": "newhire@example.com",
      "roleId": "784f2a4f-1e1b-4d4d-815b-033b1b6165ec"
    }
  }'
```

```json
{
  "data": {
    "organizationUserInvite": {
      "user": { "id": "db3bb458-...", "email": "newhire@example.com", "invitationStatus": "invited", "role": { "name": "Viewer" } },
      "errors": []
    }
  }
}
```

{% hint style="warning" %}
A non-admin inviter can only invite to **its own role**. An Admin can invite to any role. Inviting to a different role without admin rights returns `User does not have permission to invite to this role to this organization`. The `organizationRoleId` is optional; omit it to invite with no role assigned yet.
{% endhint %}

## Change a user's access level

`organizationUserUpdate` changes the role a user holds. Pass the `userId` and the target `organizationRoleId`.

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation SetRole($userId: Uuid!, $roleId: Uuid!) { organizationUserUpdate(input: { userId: $userId, organizationRoleId: $roleId }) { user { id email role { name } } errors } }",
    "variables": {
      "userId": "db3bb458-78a1-4a07-8d82-b7e091c55e58",
      "roleId": "1d6d5eff-999f-4d8b-8584-9bd0b4ce592c"
    }
  }'
```

```json
{
  "data": {
    "organizationUserUpdate": {
      "user": { "id": "db3bb458-...", "email": "newhire@example.com", "role": { "name": "Operator" } },
      "errors": []
    }
  }
}
```

## Remove a user

`organizationUserRemove` detaches a user from the organization. Pass the `userId`. This removes the membership; it does not delete the person's Interlynk account.

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation Remove($userId: Uuid!) { organizationUserRemove(input: { userId: $userId }) { user { id email } errors } }",
    "variables": { "userId": "db3bb458-78a1-4a07-8d82-b7e091c55e58" }
  }'
```

To leave an organization yourself, use `organizationUserLeave` with an `organizationId` instead.

## Custom roles

{% hint style="info" %}
Custom roles are an Enterprise feature. On other tiers the role mutations return a tier error, and you work with the built-in Admin, Operator, and Viewer roles.
{% endhint %}

### Create a role

`organizationRoleCreate` takes a name and the full list of permission keys. See the [permission reference](#permission-reference) for valid keys.

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation NewRole($name: String!, $perms: [String!]!) { organizationRoleCreate(input: { name: $name, permissions: $perms }) { organizationRole { id name permissions } errors } }",
    "variables": {
      "name": "QA Reviewer",
      "perms": ["view_organization", "view_product_group", "view_sbom"]
    }
  }'
```

### Update a role

`organizationRoleUpdate` takes permission deltas, not a full list. Each entry is a `permissionKey` and a `value`: `true` adds the permission, `false` removes it. Permissions you do not mention stay as they are.

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation EditRole($id: Uuid!, $perms: [SetPermissionInput!]) { organizationRoleUpdate(input: { organizationRoleId: $id, permissions: $perms }) { organizationRole { name permissions } errors } }",
    "variables": {
      "id": "9cba58cb-5e93-4dec-ae57-7fbc04ab3a32",
      "perms": [
        { "permissionKey": "edit_vulnerabilities", "value": true },
        { "permissionKey": "view_sbom", "value": false }
      ]
    }
  }'
```

### Assign a role to many users

`organizationRoleBulkApply` moves a list of users onto one role in a single call.

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation Bulk($id: Uuid!, $userIds: [Uuid!]!) { organizationRoleBulkApply(input: { organizationRoleId: $id, userIds: $userIds }) { organizationRole { name users { email } } errors } }",
    "variables": {
      "id": "9cba58cb-5e93-4dec-ae57-7fbc04ab3a32",
      "userIds": ["3bdc339e-afe1-435f-bebf-2d8c9d4416cb"]
    }
  }'
```

### Delete a role

`organizationRoleDelete` removes a custom role. Move any users off it first.

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation DelRole($id: Uuid!) { organizationRoleDelete(input: { id: $id }) { organizationRole { id name } errors } }",
    "variables": { "id": "9cba58cb-5e93-4dec-ae57-7fbc04ab3a32" }
  }'
```

## Single sign-on

SSO is the preferred way to run access at any scale past a handful of people. Connect your SAML identity provider once and new users are provisioned on first login with a default role, so you do not invite them one at a time. Deactivating a user in your IdP cuts their access.

{% hint style="info" %}
SSO is an Enterprise feature configured with the `samlConfig` mutations. Reading the config needs `view_connections`; creating or updating it needs `edit_connections`; deleting it needs `delete_connections`.
{% endhint %}

### Read the current SSO config

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "query { organization { samlConfig { id enabled issuer idpSsoServiceUrl tenant metadataUrl defaultUserRole { id name } } } }"}'
```

`samlConfig` is `null` until you create one.

### Create the SSO config

`samlConfigCreate` reads your identity provider's metadata URL and extracts the SSO service URL and signing certificate for you. The `defaultUserRoleId` sets the role every new SSO user receives on first login. Pick a least-privileged role such as Viewer.

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation SetupSso($role: Uuid!) { samlConfigCreate(input: { metadataUrl: \"https://idp.example.com/app/metadata\", assertionConsumerServiceUrl: \"https://api.interlynk.io/auth/saml/callback?tenant=acme\", tenant: \"acme\", issuer: \"https://api.interlynk.io\", defaultUserRoleId: $role }) { samlConfig { id enabled tenant idpSsoServiceUrl defaultUserRole { name } } errors } }",
    "variables": { "role": "784f2a4f-1e1b-4d4d-815b-033b1b6165ec" }
  }'
```

The arguments:

| Argument                      | Description                                                             |
| ----------------------------- | ----------------------------------------------------------------------- |
| `metadataUrl`                 | Your IdP's SAML metadata URL. Must be reachable; the server fetches it. |
| `assertionConsumerServiceUrl` | The ACS URL the IdP posts responses to.                                 |
| `tenant`                      | A short identifier for your organization, used in the callback URL.     |
| `issuer`                      | The service provider entity ID.                                         |
| `defaultUserRoleId`           | Role assigned to new SSO users.                                         |
| `attributeStatements`         | Optional custom attribute mappings.                                     |

If the metadata URL cannot be fetched or parsed, the mutation returns the parse errors and writes nothing.

### Update or disable SSO

`samlConfigUpdate` changes the config. `defaultUserRoleId` is required on update; the other fields are optional. Set `enabled: false` to switch SSO off without deleting the configuration.

```bash
curl https://api.interlynk.io/lynkapi \
  -H "Authorization: Bearer $INTERLYNK_SECURITY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation UpdateSso($role: Uuid!) { samlConfigUpdate(input: { enabled: false, defaultUserRoleId: $role }) { samlConfig { enabled defaultUserRole { name } } errors } }",
    "variables": { "role": "1d6d5eff-999f-4d8b-8584-9bd0b4ce592c" }
  }'
```

To remove SSO entirely, call `samlConfigDelete` with the config `id`.

## Permission reference <a href="#permission-reference" id="permission-reference"></a>

Common permission keys, by category. Read the live set for a role from the `organizationRoles { permissions }` query.

| Category        | Keys                                                                                         |
| --------------- | -------------------------------------------------------------------------------------------- |
| User Management | `view_users`, `invite_users`, `edit_user_role`, `delete_user`                                |
| Organization    | `view_organization`, `update_organization`                                                   |
| Product         | `view_product_group`, `create_product_group`, `update_product_group`, `delete_product_group` |
| SBOM            | `view_sbom`, `update_sbom`, `edit_sbom_components`, `edit_vulnerabilities`, `delete_sbom`    |
| Connections     | `view_connections`, `edit_connections`, `delete_connections`                                 |

## Errors

| Message                                                                     | Cause                                                               |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `You are not authorized to perform this action on User`                     | The token's role lacks the permission for this action.              |
| `User does not have permission to invite to this role to this organization` | A non-admin tried to invite to a role other than its own.           |
| `You are not authorized to perform this action on SamlConfig`               | The token lacks `edit_connections` (or `view_connections` to read). |
| `Metadata url could not be parsed: ...`                                     | The SSO `metadataUrl` was unreachable or not valid SAML metadata.   |

See [Errors](/api/reference/errors.md) for the general error model.
