Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions cookbook/org-admin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@

<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/org-admin" horizontal />

Every OpenHands Enterprise (OHE) user has a personal organization, and an install that predates organizations has only those. Creating a team org, and putting people in it, is an instance-level action that only a **super admin** can take. This example is a small command-line tool for those steps, with the reasons behind each one.
Every OpenHands Enterprise (OHE) user has a [personal workspace](/openhands/usage/cloud/organizations/overview#personal-workspaces-and-team-organizations), and an install that predates organizations has only those. Creating a [team org](/openhands/usage/cloud/organizations/overview#personal-workspaces-and-team-organizations), and putting people in it, is an instance-level action that only a [**super admin**](/openhands/usage/cloud/organizations/overview#super-admins) can take. This example is a small command-line tool for those steps, with the reasons behind each one.

<Note>
For how personal workspaces, team organizations, and super admins work, see the [Organizations Overview](/openhands/usage/cloud/organizations/overview). For what the VM install and Kubernetes (Helm) settings do, see [Configuring Organizations](/openhands/usage/cloud/organizations/configuring-organizations).
</Note>

## How It Works

A super admin is a user whose instance-level role is `admin`. It is separate from the `owner`, `admin` and `member` roles inside an org. It carries the permissions to create orgs, invite people into orgs it is not a member of, and grant or revoke the role itself.
A [super admin](/openhands/usage/cloud/organizations/overview#super-admins) is a user whose instance-level role is `admin`. It is separate from the `owner`, `admin` and `member` roles inside an org. It carries the permissions to create orgs, invite people into orgs it is not a member of, and grant or revoke the role itself.

```mermaid
sequenceDiagram
Expand All @@ -39,16 +43,16 @@
- To **list** super admins through the API, one of:
- the key belongs to a super admin, or
- **Enable OEM User Creation Flow** is on (see [Enabling the Feature](#enabling-the-feature)). It sets `USER_PROVISIONING_ENABLED`, which lets any signed-in user list super admins on OHE **1.62.0 or later**. Earlier versions return 403 to everyone but super admins, even with the toggle on.
- To **grant** the role or **create orgs**, the key must belong to a super admin.

Check warning on line 46 in cookbook/org-admin.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/org-admin.mdx#L46

Did you really mean 'orgs'?
- `pip install requests`

<Warning>
**Enable OEM User Creation Flow** also registers `POST /api/organizations/provision-user`, which creates Keycloak accounts with passwords and API keys. Org owners and admins can call it for their own org. It redeploys the app. Turn it off again if you only needed it to find the super admin.

Check warning on line 50 in cookbook/org-admin.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/org-admin.mdx#L50

Did you really mean 'Keycloak'?
</Warning>

## Run It

Set the server and your key. With no subcommand the tool runs a read-only preflight.

Check warning on line 55 in cookbook/org-admin.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/org-admin.mdx#L55

Did you really mean 'subcommand'?

```bash
export OH_API_BASE=https://your-ohe-host
Expand Down Expand Up @@ -102,7 +106,7 @@

<Tabs>
<Tab title="Invite">
Works for people who already have accounts. An existing user is added at their next login. If they were on their personal org, they land in the new one, and the personal org is still there. When no email provider is configured the tool prints the invite link to share.
Works for people who already have accounts. An existing user is added at their next login. If they were on their personal workspace, they land in the new one, and the personal workspace is still there. When no email provider is configured the tool prints the invite link to share.

```bash
python org_admin.py invite --org-id ORG_ID --role owner ada@example.com --yes
Expand All @@ -128,15 +132,17 @@

The endpoint is registered only when the operator turns it on. Both install types set `USER_PROVISIONING_ENABLED=true` on the app server:

| Install type | Where | Setting |
| ------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------- |
| Replicated VM | Admin console → Config → Advanced Options | Check **Enable OEM User Creation Flow**, then save the config and deploy the new version |
| Helm | Your `values.yaml` | `userProvisioning.enabled: true` |
| Install type | Where | Setting |
| ----------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------- |
| VM install | Admin console → Config → Advanced Options | Check **Enable OEM User Creation Flow**, then save the config and deploy the new version |
| Kubernetes (Helm) | Your `values.yaml` | `userProvisioning.enabled: true` |

![The Enable OEM User Creation Flow checkbox under Advanced Options in the admin console Config tab](https://raw.githubusercontent.com/OpenHands/enterprise-cookbook/main/org-admin/enable-oem-user-creation-flow.png)

When it is disabled, `POST /api/organizations/provision-user` returns `404`. Org creation and superadmin management are always available.

Check warning on line 142 in cookbook/org-admin.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/org-admin.mdx#L142

Did you really mean 'superadmin'?

For what this setting does across the installation, see [User Provisioning](/openhands/usage/cloud/organizations/configuring-organizations#user-provisioning).

## Find the Super Admin Without the API

The API route is blocked until you have a super admin key or the OEM flag. The database does not need either.
Expand All @@ -151,9 +157,9 @@

## Things to Know

- **Listing orgs only shows memberships.** `GET /api/organizations` returns orgs the caller belongs to. Keep the id from `create-org`, because a super admin cannot list the orgs it created.

Check warning on line 160 in cookbook/org-admin.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/org-admin.mdx#L160

Did you really mean 'orgs'?

Check warning on line 160 in cookbook/org-admin.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/org-admin.mdx#L160

Did you really mean 'orgs'?

Check warning on line 160 in cookbook/org-admin.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/org-admin.mdx#L160

Did you really mean 'orgs'?
- **Personal orgs are not touched.** Nothing here moves or hides personal data.
- **The default-org flags change what a team org means.** If exactly one team org exists when `default_openhands_org_enabled` is later turned on, it is adopted as the default org, and with auto-add every user who signs in joins it. With more than one team org, the default-org bootstrap does nothing.
- **Personal workspaces are not touched.** Nothing here moves or hides personal data. Hiding personal workspaces is a separate setting; see [Hide Personal Workspaces](/openhands/usage/cloud/organizations/configuring-organizations#hide-personal-workspaces).
- **The default-org flags change what a team org means.** See [Default Organization](/openhands/usage/cloud/organizations/configuring-organizations#default-organization). If exactly one team org exists when `default_openhands_org_enabled` is later turned on, it is adopted as the default org, and with auto-add every user who signs in joins it. With more than one team org, the default-org bootstrap does nothing.
- **Unclaimed git events follow the same rule.** They fall back to the default org only while exactly one team org exists.
- **Org creation is for super admins only.** The chart value `openOrgCreation.enabled` is meant to open it up, but the `POST /api/organizations` route checks the super-admin permission and does not read it.

Expand All @@ -162,7 +168,7 @@
| Endpoint | Used for |
| ------------------------------------------------- | ----------------------------------------------- |
| `GET /api/v1/users/me` | Who the key belongs to |
| `GET /api/organizations` | Orgs the caller is a member of |

Check warning on line 171 in cookbook/org-admin.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/org-admin.mdx#L171

Did you really mean 'Orgs'?
| `GET /api/admin/super-admins` | List super admins |
| `POST /api/admin/super-admins` | Grant the role by email or user id |
| `DELETE /api/admin/super-admins/{user_id}` | Revoke the role |
Expand All @@ -173,6 +179,14 @@
## Related

<CardGroup cols={2}>
<Card title="Organizations Overview" href="/openhands/usage/cloud/organizations/overview" icon="book-open">
Personal workspaces and team organizations, the current organization, super admins, and the default organization
</Card>

<Card title="Configuring Organizations" href="/openhands/usage/cloud/organizations/configuring-organizations" icon="book-open">
What the VM install and Kubernetes (Helm) settings do, and how to migrate an existing installation
</Card>

<Card title="provision-users" href="/cookbook/provision-users" icon="users-gear">
Script the same org and user provisioning from your own system, including idempotent re-runs and API key reissue
</Card>
Expand Down
32 changes: 25 additions & 7 deletions cookbook/provision-users.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Provision Users
description: Provision organizations and users as a superadmin and get a per-user API key for OEM integrations.

Check warning on line 3 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L3

Did you really mean 'superadmin'?
icon: users-gear
---

Expand All @@ -8,10 +8,14 @@

<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/provision-users" horizontal />

Create an organization, provision users into it, and get a per-user API key to act on each user's behalf, all as the superadmin of an **OpenHands Enterprise** instance. No UI and no interactive sign-up are involved.
Create an [organization](/openhands/usage/cloud/organizations/overview#key-concepts), provision users into it, and get a per-user API key to act on each user's behalf, all as the superadmin of an **OpenHands Enterprise** instance. No UI and no interactive sign-up are involved.

This is the building block for **OEM integrations**, where your own user-management system owns the user and organization lifecycle and mirrors it into OpenHands.

<Note>
For how personal workspaces, team organizations, and super admins work, see the [Organizations Overview](/openhands/usage/cloud/organizations/overview). For what the VM install and Kubernetes (Helm) settings do, see [Configuring Organizations](/openhands/usage/cloud/organizations/configuring-organizations).
</Note>

<Info>
This example is **Enterprise-only**. The endpoints do not exist on OpenHands Cloud or the open-source build. The provisioning endpoint is also **off by default**, so an operator must enable it (see [Enabling the Feature](#enabling-the-feature)).
</Info>
Expand All @@ -32,13 +36,13 @@
O-->>S: Acts as that user
```

1. `POST /api/organizations` creates the org. Only a superadmin can do this, and the creator is **not** added as a member.

Check warning on line 39 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L39

Did you really mean 'superadmin'?
2. `POST /api/organizations/provision-user` creates a user in that org. The target org goes in the **`X-Org-Id` header**, and the call bypasses email verification, terms of service, and OAuth.
3. Use the returned `api_key` as a Bearer token to act on that user's behalf.

The provision-user response holds the new user's `email`, a `password` (generated if you did not supply one, and returned only on a true create), and an `api_key` bound to the target org.

### Idempotency

Check warning on line 45 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L45

Did you really mean 'Idempotency'?

`provision-user` is idempotent. Re-running it for an email that already exists does not fail or duplicate the account. The server skips the create steps, ensures org membership, and resolves an API key. The `action` field and the HTTP status tell you which branch it took:

Expand All @@ -48,16 +52,16 @@
| `added_to_org` | 200 | User already existed and was added to this org | `null` |
| `reprovisioned` | 200 | User already existed and was already a member, so only the key was resolved | `null` |

The existing Keycloak password is never rotated, so `password` is `null` on both idempotent paths. By default the endpoint returns the user's **existing** API key. Pass `reissue_api_key: true` (or `--reissue-api-key` on the script) to delete the old key and mint a fresh one. Use this to recover a user who lost their key.

Check warning on line 55 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L55

Did you really mean 'Keycloak'?

## Prerequisites

- An OpenHands Enterprise instance with user provisioning enabled (see [Enabling the Feature](#enabling-the-feature)).
- The **superadmin** bearer token for that instance. On a fresh install, the superadmin is the first user to authenticate.

Check warning on line 60 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L60

Did you really mean 'superadmin'?

Check warning on line 60 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L60

Did you really mean 'superadmin'?
- `pip install requests`

<Note>
A superadmin can list its peers with `GET /api/admin/super-admins`. That endpoint requires superadmin, unless user provisioning is enabled on OHE 1.62.0 or later, when any signed-in user can call it. There is no self-service "am I the superadmin?" endpoint. The [`org-admin`](/cookbook/org-admin) example shows how to find the superadmin and grant the role to more users.
A superadmin can list its peers with `GET /api/admin/super-admins`. That endpoint requires superadmin, unless user provisioning is enabled on OHE 1.62.0 or later, when any signed-in user can call it. There is no self-service "am I the superadmin?" endpoint. The [`org-admin`](/cookbook/org-admin) example shows how to find the superadmin and grant the role to more users. For what the role is and is not, see [Super Admins](/openhands/usage/cloud/organizations/overview#super-admins).

Check warning on line 64 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L64

Did you really mean 'superadmin'?

Check warning on line 64 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L64

Did you really mean 'superadmin'?

Check warning on line 64 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L64

Did you really mean 'superadmin'?

Check warning on line 64 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L64

Did you really mean 'superadmin'?
</Note>

## Run It
Expand Down Expand Up @@ -95,7 +99,7 @@
The password and API key are masked in the output. Pass `--show-secrets` to print them in full.

<Note>
A superadmin has no org-scoped powers, so deleting an org needs the key of an org **owner**. When none of the demo users is an owner, the script provisions a short-lived `cleanup-…@example.com` owner for that step and deletes it with the others.

Check warning on line 102 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L102

Did you really mean 'superadmin'?
</Note>

To provision real users, name the org or the users. The script then keeps everything it creates.
Expand Down Expand Up @@ -130,7 +134,7 @@

| Flag | Environment variable | Default |
| ----------------------------------- | -------------------- | ----------------------------------------------------- |
| `--api-key` | `OH_API_KEY` | Required (superadmin token) |

Check warning on line 137 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L137

Did you really mean 'superadmin'?
| `--base-url` | `OH_API_BASE` | Required (an Enterprise instance has no standard URL) |
| `--org-name` | | A generated demo name |
| `--org-id` | | Create a new org |
Expand All @@ -143,7 +147,7 @@

## The Two Calls That Matter

Create the org. The superadmin is **not** added as a member:

Check warning on line 150 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L150

Did you really mean 'superadmin'?

```python
requests.post(
Expand Down Expand Up @@ -181,19 +185,21 @@

The endpoint is registered only when the operator turns it on. Both install types set `USER_PROVISIONING_ENABLED=true` on the app server:

| Install type | Where | Setting |
| ------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------- |
| Replicated VM | Admin console → Config → Advanced Options | Check **Enable OEM User Creation Flow**, then save the config and deploy the new version |
| Helm | Your `values.yaml` | `userProvisioning.enabled: true` |
| Install type | Where | Setting |
| ----------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------- |
| VM install | Admin console → Config → Advanced Options | Check **Enable OEM User Creation Flow**, then save the config and deploy the new version |
| Kubernetes (Helm) | Your `values.yaml` | `userProvisioning.enabled: true` |

![The Enable OEM User Creation Flow checkbox under Advanced Options in the admin console Config tab](https://raw.githubusercontent.com/OpenHands/enterprise-cookbook/main/provision-users/enable-oem-user-creation-flow.png)

When it is disabled, `POST /api/organizations/provision-user` returns `404`. Org creation and superadmin management are always available.

Check warning on line 195 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L195

Did you really mean 'superadmin'?

For what this setting does across the installation, see [User Provisioning](/openhands/usage/cloud/organizations/configuring-organizations#user-provisioning).

## Troubleshooting

<Accordion title="401 Invalid service API key">
You are calling the wrong endpoint. That error comes from the internal service-to-service routes under `/api/service/*`, such as `POST /api/service/users/{user_id}/orgs/{org_id}/api-keys`. They authenticate with the `X-Service-API-Key` header, checked against the operator-provisioned `AUTOMATIONS_SERVICE_KEY` shared secret. That key is meant for the internal automations service, not for admins.

Check warning on line 202 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L202

Did you really mean 'automations'?

User provisioning is a different flow. Call `POST /api/organizations/provision-user` with an `Authorization: Bearer <superadmin-token>` header and `X-Org-Id`. It never uses `X-Service-API-Key`. `X-Session-API-Key` (per-sandbox agent-server auth, used in other examples) is also distinct from the plain Bearer token this endpoint expects.
</Accordion>
Expand All @@ -212,22 +218,34 @@
The provision-user response is a secret. The `password` is returned only there, because the endpoint bypasses the email-based set-password flow, and the `api_key` grants access as the new user. Do not log the response, hand the credential to the user out-of-band, and always use TLS. The script masks both values unless you pass `--show-secrets`.
</Warning>

- **Guard the superadmin token.** Anyone holding it can create orgs, mint users, and grant or revoke superadmin. Treat it like a root credential.

Check warning on line 221 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L221

Did you really mean 'superadmin'?

Check warning on line 221 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L221

Did you really mean 'orgs'?

Check warning on line 221 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L221

Did you really mean 'superadmin'?
- A superadmin does **not** inherit org-scoped powers (secrets, billing, LLM settings, org deletion) in the orgs it creates.
- A superadmin does **not** inherit org-scoped powers (secrets, billing, LLM settings, org deletion) in the orgs it creates. See [Super Admins](/openhands/usage/cloud/organizations/overview#super-admins).

Check warning on line 222 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L222

Did you really mean 'superadmin'?

Check warning on line 222 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L222

Did you really mean 'orgs'?

## APIs Used

| Endpoint | Used for |
| ---------------------------------------- | ------------------------------------------------ |
| `POST /api/organizations` | Create the org (superadmin only) |

Check warning on line 228 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L228

Did you really mean 'superadmin'?
| `POST /api/organizations/provision-user` | Create or re-provision a user and get an API key |
| `GET /api/organizations/{org_id}/me` | Verify the minted key and its role |
| `DELETE /api/organizations/{org_id}` | Demo cleanup: delete the org, as an org owner |
| `DELETE /api/admin/users/{user_id}` | Demo cleanup: delete a user, as the superadmin |

Check warning on line 232 in cookbook/provision-users.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/provision-users.mdx#L232

Did you really mean 'superadmin'?

## Related

<CardGroup cols={2}>
<Card title="Organizations Overview" href="/openhands/usage/cloud/organizations/overview" icon="book-open">
Personal workspaces and team organizations, the current organization, super admins, and the default organization
</Card>

<Card title="Configuring Organizations" href="/openhands/usage/cloud/organizations/configuring-organizations" icon="book-open">
What the VM install and Kubernetes (Helm) settings do, and how to migrate an existing installation
</Card>

<Card title="org-admin" href="/cookbook/org-admin" icon="user-shield">
Find the super admin and create team orgs before you provision users into them
</Card>

<Card title="service-account-github-pat" href="/cookbook/service-account-github-pat" icon="id-card">
Act for many users from one account with per-conversation credentials
</Card>
Expand Down
Loading