B2B Onboarding ~8 min
Overview
The onboarding flow lets enterprises request access to the platform without self-registering. An admin reviews the request and approves or rejects it. On approval, an organization, user, and Keycloak account are automatically provisioned.
Prospect submits form
→ PENDING request stored
→ Admin reviews
→ APPROVED → org + user created automatically
→ REJECTED → reason stored, notifiedSubmit an onboarding request
Public endpoint — no authentication required.
http
POST /api/onboarding
Content-Type: application/json
{
"email": "cto@enterprise.com",
"firstName": "John",
"lastName": "Smith",
"companyName": "Enterprise Corp",
"type": "ENTERPRISE",
"message": "We need HRM and reporting for 200 employees."
}| Field | Required | Description |
|---|---|---|
email | Yes | Contact email (used for the provisioned user) |
firstName | Yes | |
lastName | Yes | |
companyName | Yes | Organization name |
type | Yes | ENTERPRISE, STARTUP, NON_PROFIT, GOVERNMENT |
message | No | Additional context for the reviewer |
Duplicate check
If an onboarding request for the same email already exists with status PENDING, the API returns a 409 Conflict.
List requests (admin)
http
GET /api/onboarding?status=PENDING&page=1&pageSize=20
Authorization: Bearer <shell-jwt> (requires onboarding:list)| Query param | Description |
|---|---|
status | Filter: PENDING, APPROVED, REJECTED |
page | Page number (default 1) |
pageSize | Results per page (default 20) |
Get request detail
http
GET /api/onboarding/:id
Authorization: Bearer <shell-jwt> (requires onboarding:read)Approve a request
http
PUT /api/onboarding/:id/approve
Authorization: Bearer <shell-jwt> (requires onboarding:approve)What happens in a single transaction:
- Creates organization (type from request,
ENTERPRISEplan by default) - Creates user (email, name from request, status
ACTIVE) - Assigns default roles to the user
- Attempts to create the user in Keycloak via Admin REST API
- If Keycloak user already exists (409) → continues without error
- Keycloak user is created with
requiredActions: ['UPDATE_PASSWORD', 'VERIFY_EMAIL']
- Records an audit log entry
- Updates request status →
APPROVED
json
{ "data": { "success": true, "userId": "uuid", "organizationId": "uuid" } }Keycloak user creation
The provisioned user will receive a Keycloak email to set their password and verify their email before first login (via UPDATE_PASSWORD + VERIFY_EMAIL required actions).
Reject a request
http
PUT /api/onboarding/:id/reject
Authorization: Bearer <shell-jwt> (requires onboarding:reject)
Content-Type: application/json
{ "reason": "Outside our current supported regions." }Updates status → REJECTED, stores the reason. Records an audit log entry.
Request statuses
| Status | Description |
|---|---|
PENDING | Awaiting admin review |
APPROVED | Org and user provisioned |
REJECTED | Declined with reason |
Onboarding vs self-registration
Self-registration (POST /auth/register) | Onboarding (POST /onboarding → approve) | |
|---|---|---|
| Auth required | No | No (submit) / Yes (approve) |
| Plan | User selects at registration | ENTERPRISE default |
| Org type | Derived from email domain | Set in request |
| Keycloak user | No (local credentials only) | Yes (created via Admin API) |
| Review step | None | Admin must approve |