Summary and recommendation
OpenShift exposes user and group management through the `user.openshift.io/v1` API group, served at `https://<cluster-api-server>/apis/user.openshift.io/v1`. Authentication requires a Bearer token - either an OAuth access token obtained via `oc login` or the OAuth authorize endpoint, or a service account token with an appropriate ClusterRoleBinding granting verbs over `users`, `groups`, and `identities` resources.
There is no SCIM 2.0 endpoint on any OpenShift deployment tier - self-managed, ROSA, or ARO. Automated provisioning must go through direct REST calls to the `user.openshift.io/v1` API, LDAP group sync jobs, or just-in-time IDP login.
Building an identity graph across OpenShift requires correlating three separate object types: User objects (`/users`), Identity objects (`/identities`), and Group objects (`/groups`), since membership and IDP linkage are stored independently.
Rate limiting is enforced by Kubernetes API Priority and Fairness (APF), not a fixed published limit. When throttled, the server returns HTTP 429 with a `Retry-After` header. Pagination uses an opaque `continue` token, not offset; the token is time-limited and returns HTTP 410 Gone if it expires mid-iteration.
API quick reference
| Has user API | Yes |
| Auth method | Bearer token (OpenShift OAuth token or service account token); cluster also supports OAuth 2.0 via configured identity providers |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | N/A - not available on any plan |
Authentication
Auth method: Bearer token (OpenShift OAuth token or service account token); cluster also supports OAuth 2.0 via configured identity providers
Setup steps
- Obtain an OAuth access token via
oc loginor POST to https:///oauth/authorize with configured identity provider credentials. - Alternatively, create a ServiceAccount and extract its token secret:
oc create serviceaccount <name> -n <namespace>thenoc get secret <sa-token-secret> -o jsonpath='{.data.token}' | base64 -d. - Grant the service account appropriate ClusterRole (e.g., cluster-admin or a custom role with user/group RBAC verbs) via ClusterRoleBinding.
- Pass the token in the Authorization header:
Authorization: Bearer <token>. - For external IdP (OIDC/SAML), configure the OAuth server CR at
cluster.config.openshift.io/v1with the identityProviders field.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| user:full | Full read/write access to the authenticated user's account and all API groups | Full user and group management via OAuth token |
| user:info | Read-only access to the authenticated user's identity information | GET /apis/user.openshift.io/v1/users/~ (current user info) |
| user:list-scoped-projects | List projects the user has access to | Project listing for the authenticated user |
| cluster-admin ClusterRole (RBAC) | Not an OAuth scope but a ClusterRoleBinding granting full cluster access; required for managing all users/groups | Creating, deleting, or listing all users and groups cluster-wide |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| metadata.name | string | Unique username; serves as the primary identifier for the User object | required | immutable | Corresponds to the login name from the identity provider |
| metadata.uid | string | System-generated unique identifier (UUID) | server-assigned | immutable | Set by the API server; cannot be specified by client |
| metadata.labels | map[string]string | Key-value labels for selection and organization | optional | mutable | Used for RBAC selectors and tooling |
| metadata.annotations | map[string]string | Arbitrary metadata annotations | optional | mutable | |
| fullName | string | Human-readable display name of the user | optional | mutable | Populated from IdP claims if configured |
| identities | []string | List of identity references in format |
optional | mutable | Links the User to one or more Identity objects; managed by OAuth server on first login |
| groups | []string | List of group names the user belongs to (read-only in User object; managed via Group objects) | read-only | read-only | Modify group membership via the Group resource, not the User resource |
| apiVersion | string | API version; always user.openshift.io/v1 | required | required | |
| kind | string | Object kind; always User | required | required |
Core endpoints
List all users
- Method: GET
- URL:
https://<cluster-api>/apis/user.openshift.io/v1/users - Watch out for: Uses Kubernetes list pagination (limit + continue token), not offset. Iterate using the continue field until it is empty.
Request example
GET /apis/user.openshift.io/v1/users?limit=50 HTTP/1.1
Authorization: Bearer <token>
Accept: application/json
Response example
{
"kind": "UserList",
"apiVersion": "user.openshift.io/v1",
"metadata": {"continue": "<token>"},
"items": [{"metadata":{"name":"jdoe"},"fullName":"Jane Doe"}]
}
Get a specific user
- Method: GET
- URL:
https://<cluster-api>/apis/user.openshift.io/v1/users/{name} - Watch out for: Use name
~to retrieve the currently authenticated user: GET /apis/user.openshift.io/v1/users/~
Request example
GET /apis/user.openshift.io/v1/users/jdoe HTTP/1.1
Authorization: Bearer <token>
Response example
{
"kind": "User",
"apiVersion": "user.openshift.io/v1",
"metadata": {"name": "jdoe", "uid": "abc-123"},
"fullName": "Jane Doe",
"identities": ["ldap:uid=jdoe,ou=users,dc=example,dc=com"]
}
Delete a user
- Method: DELETE
- URL:
https://<cluster-api>/apis/user.openshift.io/v1/users/{name} - Watch out for: Deleting a User object does NOT delete the associated Identity object or revoke active OAuth tokens. You must also DELETE /apis/user.openshift.io/v1/identities/
and revoke OAuthAccessTokens separately.
Request example
DELETE /apis/user.openshift.io/v1/users/jdoe HTTP/1.1
Authorization: Bearer <token>
Response example
{
"kind": "Status",
"status": "Success",
"code": 200
}
Update user (patch fullName or labels)
- Method: PATCH
- URL:
https://<cluster-api>/apis/user.openshift.io/v1/users/{name} - Watch out for: Supports strategic-merge-patch, merge-patch+json, and json-patch. The groups field is read-only on the User object; patch it via the Group resource.
Request example
PATCH /apis/user.openshift.io/v1/users/jdoe HTTP/1.1
Content-Type: application/merge-patch+json
Authorization: Bearer <token>
{"fullName": "Jane A. Doe"}
Response example
{
"kind": "User",
"metadata": {"name": "jdoe"},
"fullName": "Jane A. Doe"
}
List all groups
- Method: GET
- URL:
https://<cluster-api>/apis/user.openshift.io/v1/groups - Watch out for: Group membership is stored in the users[] array on the Group object, not on the User object.
Request example
GET /apis/user.openshift.io/v1/groups?limit=50 HTTP/1.1
Authorization: Bearer <token>
Response example
{
"kind": "GroupList",
"items": [{"metadata":{"name":"dev-team"},"users":["jdoe","bsmith"]}]
}
Create or update a group (add members)
- Method: PUT
- URL:
https://<cluster-api>/apis/user.openshift.io/v1/groups/{name} - Watch out for: PUT replaces the entire users[] array. Use PATCH with json-patch to add/remove individual members atomically.
Request example
PUT /apis/user.openshift.io/v1/groups/dev-team HTTP/1.1
Content-Type: application/json
Authorization: Bearer <token>
{"apiVersion":"user.openshift.io/v1","kind":"Group","metadata":{"name":"dev-team"},"users":["jdoe","bsmith","anew"]}
Response example
{
"kind": "Group",
"metadata": {"name": "dev-team"},
"users": ["jdoe", "bsmith", "anew"]
}
List identities
- Method: GET
- URL:
https://<cluster-api>/apis/user.openshift.io/v1/identities - Watch out for: Identity objects are auto-created on first login. Manually creating them without a matching User object will cause login failures.
Request example
GET /apis/user.openshift.io/v1/identities HTTP/1.1
Authorization: Bearer <token>
Response example
{
"kind": "IdentityList",
"items": [{"metadata":{"name":"ldap:uid=jdoe"},"user":{"name":"jdoe"},"providerName":"ldap"}]
}
Delete an OAuth access token (revoke session)
- Method: DELETE
- URL:
https://<cluster-api>/apis/oauth.openshift.io/v1/oauthaccesstokens/{token-name} - Watch out for: Token names are SHA256-hashed. List tokens for a user by filtering: GET /apis/oauth.openshift.io/v1/oauthaccesstokens?fieldSelector=userName=jdoe
Request example
DELETE /apis/oauth.openshift.io/v1/oauthaccesstokens/<sha256~...> HTTP/1.1
Authorization: Bearer <admin-token>
Response example
{
"kind": "Status",
"status": "Success"
}
Rate limits, pagination, and events
- Rate limits: OpenShift does not publish fixed global API rate limits. Rate limiting is enforced by the Kubernetes API server's built-in flow control (APF - API Priority and Fairness) introduced in Kubernetes 1.20+, which OpenShift 4.x uses. Limits are configurable per cluster by administrators via FlowSchema and PriorityLevelConfiguration objects.
- Rate-limit headers: No
- Retry-After header: Yes
- Rate-limit notes: When throttled, the API server returns HTTP 429 with a Retry-After header. Administrators can tune APF FlowSchemas. Default kube-apiserver max-requests-inflight is 400 and max-mutating-requests-inflight is 200 but these are cluster-configurable.
- Pagination method: token
- Default page size: 0
- Max page size: 0
- Pagination pointer: limit / continue
| Plan | Limit | Concurrent |
|---|---|---|
| Self-managed / ROSA / ARO (all tiers) | Configurable via API Priority and Fairness (APF); no fixed published limit | 0 |
- Webhooks available: No
- Webhook notes: OpenShift does not provide native outbound webhooks for user lifecycle events (create/delete/update). The Kubernetes API server supports Admission Webhooks (ValidatingWebhookConfiguration, MutatingWebhookConfiguration) which can intercept API calls to user/group resources, but these are inbound validation hooks, not outbound event notifications.
- Alternative event strategy: Use Kubernetes watch API (GET /apis/user.openshift.io/v1/users?watch=true) for real-time change streams, or configure audit logging to an external SIEM. For IdP-driven provisioning events, use LDAP sync jobs (oc adm groups sync) on a schedule.
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: N/A - not available on any plan
- Endpoint: Not documented
Limitations:
- OpenShift has no native SCIM 2.0 endpoint.
- User provisioning is handled via identity provider login (just-in-time), LDAP group sync (oc adm groups sync), or direct REST API calls.
- Red Hat SSO (Keycloak) can be deployed alongside OpenShift and does support SCIM via third-party extensions, but this is not part of OpenShift itself.
- ROSA and ARO managed services also lack native SCIM; they rely on OIDC/SAML IdP integration.
Common scenarios
Full user deprovisioning requires three sequential API calls minimum: DELETE all OAuthAccessTokens for the user (filtered via fieldSelector=userName=<username>), DELETE the User object at `/apis/user. openshift.
io/v1/users/, and DELETE the Identity object at /apis/user. openshift.
io/v1/identities/
Group membership is managed exclusively on the Group object's users[] array - the groups[] field on the User object is read-only and computed. Use PATCH with application/json-patch+json to add or remove individual members atomically; a PUT replaces the entire users[] array and risks race conditions in concurrent environments.
For service account automation, OCP 4.11+ no longer auto-creates long-lived token secrets. Use oc create token (TokenRequest API) for bounded tokens, or explicitly create a kubernetes.io/service-account-token Secret for a persistent credential. Bind a least-privilege ClusterRole scoped to users, groups, and identities resources rather than using cluster-admin in production.
Deprovision a user completely
- List and delete all OAuthAccessTokens for the user: GET /apis/oauth.openshift.io/v1/oauthaccesstokens?fieldSelector=userName=
, then DELETE each token. - Delete the User object: DELETE /apis/user.openshift.io/v1/users/
. - Find and delete the Identity object: GET /apis/user.openshift.io/v1/identities, filter by user.name=
, then DELETE /apis/user.openshift.io/v1/identities/ . - Remove the user from all Group objects: PATCH each Group's users[] array to exclude the username.
- Remove any ClusterRoleBindings or RoleBindings referencing the user: oc get clusterrolebindings -o json | grep
, then delete relevant bindings.
Watch out for: Skipping step 3 leaves a dangling Identity; if the same IdP account logs in again, OpenShift will re-create the User object automatically.
Bulk sync LDAP groups to OpenShift groups
- Create an LDAP sync config file (ldap-sync.yaml) specifying url, bindDN, bindPassword, and groupUIDNameMapping.
- Dry-run to preview:
oc adm groups sync --sync-config=ldap-sync.yaml. - Apply the sync:
oc adm groups sync --sync-config=ldap-sync.yaml --confirm. - Verify groups created/updated:
oc get groups. - Schedule as a CronJob or external cron to keep groups in sync continuously.
Watch out for: LDAP sync creates Group objects and populates users[] but does NOT create User objects. Users are only created in OpenShift upon their first login via the LDAP identity provider.
Grant a service account permission to manage users via API
- Create a service account:
oc create serviceaccount user-manager -n openshift-config. - Create a ClusterRole with required verbs:
oc create clusterrole user-manager-role --verb=get,list,watch,create,update,patch,delete --resource=users,groups,identities. - Bind the role:
oc create clusterrolebinding user-manager-binding --clusterrole=user-manager-role --serviceaccount=openshift-config:user-manager. - Extract the service account token:
oc create token user-manager -n openshift-config --duration=8760h(OCP 4.11+ uses TokenRequest API). - Use the token in API calls:
Authorization: Bearer <token>.
Watch out for: On OCP 4.11+, long-lived service account token secrets are no longer auto-created. Use oc create token (TokenRequest) for bounded tokens or explicitly create a Secret of type kubernetes.io/service-account-token for a persistent token.
Why building this yourself is a trap
The most common integration failure is treating the User object as the authoritative identity record. It is not. The identity graph in OpenShift spans User objects, Identity objects, OAuthAccessTokens, Group memberships, ClusterRoleBindings, and RoleBindings - all stored as separate Kubernetes resources with no single API call to retrieve the full picture for a given principal.
Any automation that only operates on /users will produce an incomplete and potentially misleading view of who has access to what.
A second trap is the just-in-time provisioning model. LDAP group sync populates Group objects and their users[] arrays, but User objects are not created until the user authenticates.
An identity graph built from /users alone will be missing every user who has been synced into a group but has not yet logged in - a silent gap that affects access audits.
Webhooks for user lifecycle events do not exist natively. The Kubernetes watch API (GET /apis/user.openshift.io/v1/users?watch=true) provides a real-time change stream, but it requires a persistent connection and client-side reconnect logic. For event-driven provisioning pipelines, this is the only supported mechanism short of polling or external audit log ingestion.
Automate OpenShift workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.