Febasidocs
Guides

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:

  • JWTAuthorization: Bearer <admin-jwt> for in-product admin user creation.
  • Client KeyX-API-Key: ck_... plus X-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!"
}
FieldTypeRequiredNotes
emailstringone-ofValid email format.
usernamestringone-of3–100 chars.
cpfCnpjstringone-ofExactly 11 (CPF) or 14 (CNPJ) digits.
passwordstringyesMinimum 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: 8
  • requireUppercase: true
  • requireNumbers: true
  • requireSpecialChars: 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

StatusCodeWhen
400VALIDATION_ERRORNone of email/username/cpfCnpj provided, or invalid format.
400PASSWORD_POLICY_VIOLATIONPassword fails the tenant's policy.
401UNAUTHORIZEDNo JWT / no Client Key on the request.
401MISSING_TENANT_CONTEXTClient Key call without X-Tenant-Code.
403INSUFFICIENT_SCOPEClient Key lacks the users:create scope.
403FORBIDDENJWT principal lacks the users:create permission.
409CONFLICTEmail, 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 observability

The 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/login

with 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.

On this page