Registering users
Create users inside a tenant — body shape, identifier rules, password policy, and the follow-up role assignment.
POST /api/v1/register creates a user inside the caller's tenant. It
accepts two authentication paths, both requiring the users:create
permission:
- JWT —
Authorization: Bearer <admin-jwt>for in-product admin user creation. - Client Key —
X-API-Key: ck_...plusX-Tenant-Code: <tenant-code>for tenant backends that proxy public signup requests. This is the recommended path for public signup pages.
Endpoint
POST /api/v1/register
Authorization: Bearer <admin-jwt>
Content-Type: application/json{
"email": "alice@acme.com",
"username": "alice",
"cpfCnpj": "12345678901",
"password": "Strong-Pass-12!"
}| Field | Type | Required | Notes |
|---|---|---|---|
email | string | one-of | Valid email format. |
username | string | one-of | 3–100 chars. |
cpfCnpj | string | one-of | Exactly 11 (CPF) or 14 (CNPJ) digits. |
password | string | yes | Minimum 8 chars; further constraints from the tenant's policy. |
At least one of email, username, or cpfCnpj must be present.
The endpoint creates the user, hashes the password with bcrypt (cost 12),
sets status to ACTIVE, and logs a REGISTER audit event.
Success response
{
"success": true,
"data": {
"id": "01HXY...",
"email": "alice@acme.com",
"username": "alice",
"cpfCnpj": "12345678901",
"tenantId": "01HX9...",
"createdAt": "2026-05-22T18:14:02.103Z"
}
}HTTP status is 201 Created.
Password policy
The password is validated against the tenant's authConfig.passwordPolicy
before hashing. Defaults:
minLength: 8requireUppercase: truerequireNumbers: truerequireSpecialChars: false
Violations return:
{
"success": false,
"code": "PASSWORD_POLICY_VIOLATION",
"error": "Password does not meet policy requirements: ...",
"details": { "violations": ["must contain at least one uppercase letter"] }
}Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | None of email/username/cpfCnpj provided, or invalid format. |
| 400 | PASSWORD_POLICY_VIOLATION | Password fails the tenant's policy. |
| 401 | UNAUTHORIZED | No JWT / no Client Key on the request. |
| 401 | MISSING_TENANT_CONTEXT | Client Key call without X-Tenant-Code. |
| 403 | INSUFFICIENT_SCOPE | Client Key lacks the users:create scope. |
| 403 | FORBIDDEN | JWT principal lacks the users:create permission. |
| 409 | CONFLICT | Email, username, or CPF/CNPJ already taken inside the tenant. |
Assigning roles
Registration does not assign any role. A new user can authenticate but will have zero permissions until a role (or individual permissions) are granted. Make the follow-up call:
POST /api/v1/roles/assign
Authorization: Bearer <admin-jwt>{
"userId": "01HXY...",
"roleId": "01HX5..."
}See Authorization with RBAC for role and permission management.
Public signup: the server-proxy pattern
For tenant applications that need a public signup page, the recommended integration is a server-side proxy owned by the tenant. The browser posts to the tenant's own endpoint; the tenant's backend forwards the validated request to Febasi Auth using a Client Key that never reaches the browser.
Browser ──signup form──▶ Tenant Backend ──ck_... + X-Tenant-Code──▶ Febasi Auth
│
├── captcha / fraud / business validation
├── per-tenant rate limit
└── retries and observabilityThe Client Key sits in the tenant backend with a single scope
(users:create) — a leak is bounded to user creation inside that tenant.
A browser-direct path using a publishable key (pk_*) is planned. Until
then, the server-proxy pattern is the canonical solution.
End-to-end provisioning flow
Authenticate as an admin
POST /api/v1/loginwith credentials of a user whose role carries users:create. The default
super_admin role inside any tenant has this permission.
Create the user
POST /api/v1/register with the body above. Capture the returned id.
Assign a role
POST /api/v1/roles/assign with the userId from step 2 and the desired
roleId. The role's hierarchy level must be below the actor's level
or the call returns HIERARCHY_VIOLATION (403).
Hand the credentials over
The user can now authenticate via /login with the password set in
step 2. Tokens carry the freshly assigned permissions immediately.