Summary and recommendation
Sourcegraph exposes user management through a single GraphQL endpoint at /.api/graphql and a SCIM 2.0 endpoint at /.api/scim/v2 (Enterprise only). All user-management operations require a site-admin personal access token with site-admin:sudo scope sent as Authorization: token <TOKEN>. Standard user tokens are rejected for any cross-user operation.
The GraphQL API returns HTTP 200 even on errors - always inspect the errors array in the response body, not the HTTP status code.
Integrating Sourcegraph into an identity graph requires treating the opaque base64-encoded user ID as the stable join key across systems; do not construct these IDs manually, always resolve them via a users query first.
API quick reference
| Has user API | Yes |
| Auth method | Access token (Bearer) – site-admin personal access token sent as Authorization: token <TOKEN> |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Enterprise |
Authentication
Auth method: Access token (Bearer) – site-admin personal access token sent as Authorization: token
Setup steps
- Log in to Sourcegraph as a site administrator.
- Navigate to User Settings → Access tokens.
- Click 'Generate new token', provide a description, and select the desired permission scope (site-admin:sudo for full user management).
- Copy the generated token; it is shown only once.
- Include the token in all API requests as the HTTP header: Authorization: token
.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| user:all | Read and write access to the authenticated user's account data. | Reading/updating own user profile via GraphQL |
| site-admin:sudo | Allows acting as any user; required for site-admin user management operations. | Creating, listing, updating, deleting, and promoting other users via GraphQL or SCIM |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | ID (GraphQL scalar) | Opaque internal user ID. | system-assigned | immutable | Used as the primary key in all GraphQL mutations. |
| username | string | Unique login handle for the user. | required | updatable via updateUser mutation | Must be unique across the instance; used in @-mentions. |
| string | Primary email address. | required | updatable | Email verification may be required depending on site config. | |
| displayName | string | Human-readable full name. | optional | updatable | Shown in the UI; maps to SCIM displayName. |
| avatarURL | string (URL) | URL of the user's avatar image. | optional | updatable | Can be set to a Gravatar or custom URL. |
| siteAdmin | boolean | Whether the user has site-administrator privileges. | optional (default false) | updatable via setUserIsSiteAdmin mutation | Requires site-admin:sudo scope to promote another user. |
| createdAt | DateTime | Timestamp when the account was created. | system-assigned | immutable | |
| updatedAt | DateTime | Timestamp of last profile update. | system-assigned | system-assigned | |
| builtinAuth | boolean | Whether the user has a built-in (username/password) credential. | depends on auth provider config | read-only via API | |
| organizations | OrgConnection | Organizations the user belongs to. | not set at creation | managed via addUserToOrganization / removeUserFromOrganization mutations | Returned as a GraphQL connection. |
| externalAccounts | ExternalAccountConnection | Linked external auth provider accounts (SAML, OIDC, GitHub, etc.). | system-assigned on first SSO login | read-only via API | Used by SCIM to correlate IdP identities. |
| scimExternalId | string | SCIM externalId assigned by the IdP. | set by SCIM provisioner | managed by SCIM | Only present when SCIM provisioning is active. |
| active | boolean | SCIM active flag; false suspends the user. | true by default | updatable via SCIM PATCH or invalidateSessionsByID mutation | Deactivating via SCIM revokes all sessions. |
Core endpoints
List users
- Method: POST
- URL:
https://<instance>/.api/graphql - Watch out for: Requires site-admin token; non-admin tokens can only query their own user.
Request example
{
"query": "query { users(first: 20) { nodes { id username email siteAdmin } pageInfo { hasNextPage endCursor } } }"
}
Response example
{
"data": {
"users": {
"nodes": [{"id":"VXNlcjox","username":"alice","email":"alice@example.com","siteAdmin":false}],
"pageInfo": {"hasNextPage":false,"endCursor":null}
}
}
}
Get user by username
- Method: POST
- URL:
https://<instance>/.api/graphql - Watch out for: Returns null (not an error) if the username does not exist.
Request example
{
"query": "query { user(username: \"alice\") { id username email displayName siteAdmin createdAt } }"
}
Response example
{
"data": {
"user": {"id":"VXNlcjox","username":"alice","email":"alice@example.com","displayName":"Alice Smith","siteAdmin":false,"createdAt":"2024-01-10T09:00:00Z"}
}
}
Create user (site admin)
- Method: POST
- URL:
https://<instance>/.api/graphql - Watch out for: resetPasswordURL is only returned once; store it immediately. Requires site-admin:sudo scope.
Request example
{
"query": "mutation { createUser(username: \"bob\", email: \"bob@example.com\") { user { id username } resetPasswordURL } }"
}
Response example
{
"data": {
"createUser": {
"user": {"id":"VXNlcjoy","username":"bob"},
"resetPasswordURL": "https://<instance>/password-reset?code=..."
}
}
}
Update user profile
- Method: POST
- URL:
https://<instance>/.api/graphql - Watch out for: The mutation returns alwaysNil; success is indicated by absence of errors in the response.
Request example
{
"query": "mutation { updateUser(user: \"VXNlcjoy\", username: \"bob2\", displayName: \"Bob Jones\") { alwaysNil } }"
}
Response example
{
"data": { "updateUser": { "alwaysNil": null } }
}
Delete user
- Method: POST
- URL:
https://<instance>/.api/graphql - Watch out for: hard: true permanently purges the user and all associated data. hard: false (soft delete) is the safer default. Irreversible for hard deletes.
Request example
{
"query": "mutation { deleteUser(user: \"VXNlcjoy\", hard: false) { alwaysNil } }"
}
Response example
{
"data": { "deleteUser": { "alwaysNil": null } }
}
Promote/demote site admin
- Method: POST
- URL:
https://<instance>/.api/graphql - Watch out for: Cannot demote the last remaining site admin; the API will return an error.
Request example
{
"query": "mutation { setUserIsSiteAdmin(userID: \"VXNlcjox\", siteAdmin: true) { alwaysNil } }"
}
Response example
{
"data": { "setUserIsSiteAdmin": { "alwaysNil": null } }
}
Invalidate user sessions
- Method: POST
- URL:
https://<instance>/.api/graphql - Watch out for: Forces immediate logout of all active sessions for the user. Use when deprovisioning outside of SCIM.
Request example
{
"query": "mutation { invalidateSessionsByID(userID: \"VXNlcjox\") { alwaysNil } }"
}
Response example
{
"data": { "invalidateSessionsByID": { "alwaysNil": null } }
}
SCIM – List users
- Method: GET
- URL:
https://<instance>/.api/scim/v2/Users - Watch out for: SCIM endpoint requires Enterprise plan with SSO (SAML or OIDC) configured. The Bearer token must be a site-admin personal access token.
Request example
GET /.api/scim/v2/Users?startIndex=1&count=100
Authorization: Bearer <site-admin-token>
Response example
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 2,
"startIndex": 1,
"itemsPerPage": 100,
"Resources": [{"id":"VXNlcjox","userName":"alice","active":true}]
}
Rate limits, pagination, and events
- Rate limits: Sourcegraph does not publish fixed numeric rate limits for its GraphQL API in official documentation. Rate limiting behavior is configurable by site admins via site configuration (rateLimits settings). SCIM endpoints follow standard HTTP 429 responses when limits are hit.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: When rate-limited, the API returns HTTP 429 with a Retry-After header. Site admins can tune limits in site config under rateLimits. Sourcegraph Cloud may enforce additional limits not publicly documented.
- Pagination method: cursor
- Default page size: 20
- Max page size: 1000
- Pagination pointer: first / after (GraphQL connection arguments); SCIM uses startIndex + count
| Plan | Limit | Concurrent |
|---|---|---|
| All plans (self-hosted) | Configurable by site admin; no publicly documented default ceiling | 0 |
- Webhooks available: No
- Webhook notes: Sourcegraph does not offer native outbound webhooks for user lifecycle events (create, update, delete). Outbound webhooks exist for code-related events (e.g., batch changes) but not user management.
- Alternative event strategy: Poll the GraphQL users query on a schedule, or rely on SCIM push provisioning from the IdP (Okta, Entra ID) to receive real-time user lifecycle events.
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Enterprise
Endpoint: https://
/.api/scim/v2 Supported operations: GET /Users – list all users, GET /Users/{id} – get single user, POST /Users – provision new user, PUT /Users/{id} – replace user attributes, PATCH /Users/{id} – partial update (including active flag for deactivation), DELETE /Users/{id} – deprovision user, GET /ServiceProviderConfig – SCIM capability discovery, GET /Schemas – attribute schema discovery
Limitations:
- Requires Enterprise plan; not available on Free or Enterprise Starter.
- SSO (SAML or OIDC) must be configured and active before enabling SCIM.
- Group provisioning (SCIM /Groups) is not supported as of the latest documented version.
- Only Okta and Microsoft Entra ID (Azure AD) are officially documented as tested IdP integrations.
- SCIM token must be a site-admin personal access token; no dedicated SCIM service account token type.
- Deactivating a user via SCIM (active: false) suspends the account but does not hard-delete it.
Common scenarios
Three scenarios cover the majority of programmatic user management needs.
SCIM provisioning from Okta or Entra ID: Requires Enterprise plan, active SAML/OIDC SSO, and a site-admin token set as the scim.authToken in site config. The IdP sends POST /Users on assignment; Sourcegraph creates the account and the user authenticates via SSO immediately. If SSO is not configured before SCIM is enabled, provisioning calls succeed but the user has no login method.
Deprovision on offboarding: Preferred path is SCIM - remove the user from the IdP app assignment, which triggers PATCH active: false and optionally DELETE. For manual or scripted offboarding, call invalidateSessionsByID first to revoke all active sessions immediately, then deleteUser with hard: false for a soft delete that preserves audit history. Reserve hard: true for cases where full data purge is explicitly required; it is irreversible and removes access tokens, saved searches, and code insights.
Bulk user enumeration for identity graph reconciliation: POST to /.api/graphql with users(first: 1000) selecting id, username, email, siteAdmin, and createdAt. Paginate using the endCursor from pageInfo until hasNextPage is false. The maximum page size is 1000; omitting cursor-based pagination on large instances silently truncates results. Filter siteAdmin === false to isolate standard users for downstream processing.
Provision a new developer via SCIM from Okta
- Ensure Enterprise plan is active and SAML/OIDC SSO is configured in Sourcegraph site config.
- In Sourcegraph site config, set 'scim.authToken' to a site-admin personal access token.
- In Okta, add the Sourcegraph SCIM app and configure the base URL to https://
/.api/scim/v2 with Bearer token auth. - Assign the user to the Okta app; Okta sends POST /Users with userName, emails, displayName, and externalId.
- Sourcegraph creates the account; the user can log in via SSO immediately.
Watch out for: If SSO is not configured first, SCIM provisioning will succeed but the user will have no login method and cannot authenticate.
Deprovision a user when they leave the organization
- Via SCIM (preferred): Remove the user from the Okta/Entra app assignment; the IdP sends PATCH /Users/{id} with active: false, then optionally DELETE /Users/{id}.
- Via GraphQL (manual): Execute invalidateSessionsByID mutation to immediately revoke all sessions.
- Then execute deleteUser mutation with hard: false to soft-delete the account.
- Verify the user no longer appears in the users query.
Watch out for: Soft delete (hard: false) retains audit history. Hard delete (hard: true) is permanent and removes all associated data including access tokens and saved searches.
Bulk-list all users and identify non-admin accounts
- Send a POST to /.api/graphql with query: { users(first: 1000) { nodes { id username email siteAdmin createdAt } pageInfo { hasNextPage endCursor } } }
- If pageInfo.hasNextPage is true, repeat the query with after: "
" until hasNextPage is false. - Filter the collected nodes where siteAdmin === false to identify standard users.
- Use the returned IDs for subsequent update or delete mutations as needed.
Watch out for: The maximum first value is 1000 per page. For instances with more than 1000 users, cursor-based pagination is mandatory; omitting it will silently truncate results.
Why building this yourself is a trap
Several API behaviors are non-obvious and will cause silent failures if not handled explicitly. The createUser mutation returns a one-time resetPasswordURL - if not captured in the same response, a new password-reset link must be generated separately; there is no way to retrieve it again.
The updateUser mutation returns alwaysNil on success, so success detection requires checking for the absence of errors rather than a meaningful return value. SCIM /Groups is not supported; group and team membership must be managed through GraphQL organization mutations instead.
Sourcegraph Cloud instances may enforce rate limits and endpoint availability that differ from self-hosted behavior, and these are not publicly documented - Cloud-specific limits require direct engagement with Sourcegraph support.
For teams building an identity graph with 60+ deep IT/identity integrations via an MCP server, the opaque ID format and the absence of outbound user-lifecycle webhooks mean the integration layer must poll the GraphQL users query on a schedule or rely entirely on SCIM push events from the IdP for real-time state.
Automate Sourcegraph 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.