Authentication endpoints
/auth/* group — account creation, login, session management, password reset and organization invitations.
Endpoint table
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /auth/bootstrap | Public (1x) | Bootstrap the first admin when the instance is empty. |
| GET | /auth/bootstrap-status | Public | Check whether bootstrap is still available. |
| POST | /auth/register-organization | Public | Create a new organization and its first admin. |
| POST | /auth/login | Public | Email + password login. Returns session + cookie. |
| GET | /auth/me | Session | Current session, user and organizations. |
| PATCH | /auth/me/locale | Session | Update the user's language preference. |
| POST | /auth/logout | Session | End the current session. |
| POST | /auth/password-reset/request | Public | Request a password-reset email. |
| POST | /auth/password-reset/confirm | Public | Confirm a new password with the email token. |
| GET | /auth/organizations/{organization_id}/members | Session | List organization members. |
| POST | /auth/organizations/{organization_id}/members | Session (member:manage) | Add a member directly (no invitation). |
| GET | /auth/organizations/{organization_id}/invitations | Session | List pending invitations. |
| POST | /auth/organizations/{organization_id}/invitations | Session (member:manage) | Create an email invitation. |
| GET | /auth/invitations/{token} | Public | Look up an invitation by token (for the accept screen). |
| POST | /auth/invitations/{token}/accept | Public | Accept the invitation and create the user. |
POST /auth/bootstrap
Creates the first administrator and the first organization on a blank instance. Runs only once — afterwards it responds 409.
Request body (AuthBootstrapRequest):
{
"organization_name": "Acme Security",
"organization_description": "Internal SOC",
"email": "admin@acme.com",
"display_name": "Initial Admin",
"password": "strong-password-here"
}
Validations:
organization_name— 1–120 chars.email— valid RFC, 3–254 chars.display_name— 1–120 chars.password— at least 10 chars.
Response 200 (AuthSessionResponse) — see session structure.
Errors:
409 conflict— bootstrap already done. Use/auth/login.
GET /auth/bootstrap-status
Tells whether bootstrap is still open and whether self-signup is enabled.
Response 200:
{
"bootstrap_available": false,
"registration_enabled": true
}
POST /auth/register-organization
Same schema as bootstrap, but creates a new organization in an instance that already has others. Depends on NATIVE_WAZUH_SELF_SIGNUP_ENABLED=true.
Request body: identical to AuthBootstrapRequest.
Response 200: AuthSessionResponse.
Errors:
403 forbidden— self-signup disabled.409 conflict— email already registered.429 rate_limited— more than 10 registrations/hour per IP.
POST /auth/login
Email + password login. Returns the session in the body and sets a cookie.
Request body (LoginRequest):
{
"email": "admin@acme.com",
"password": "strong-password-here",
"organization_id": "org_abc123"
}
organization_idis optional — used when the user belongs to multiple organizations and wants to pick one explicitly.
Response 200 (AuthSessionResponse):
{
"access_token": "eyJhbGciOi…",
"token_type": "bearer",
"expires_at": "2026-04-25T03:30:00Z",
"user": {
"id": "usr_...",
"email": "admin@acme.com",
"display_name": "Initial Admin",
"is_active": true,
"is_platform_admin": false,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-04-24T12:00:00Z"
},
"organizations": [ { "id": "org_abc123", "slug": "acme", "name": "Acme Security", "role": "org_admin", "is_active": true, "...": "..." } ],
"current_organization": { "id": "org_abc123", "...": "..." },
"memberships": [ { "id": "mem_...", "organization_id": "org_abc123", "user_id": "usr_...", "role": "org_admin", "...": "..." } ]
}
Besides the body, the response also sets the cookie:
Set-Cookie: ruleforge_session=<jwt>; HttpOnly; SameSite=Lax
curl example:
curl -X POST "$RF_BASE/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"admin@acme.com","password":"strong-password-here"}'
Errors:
401 unauthorized— invalid email/password.403 forbidden— user inactive or organization suspended.429 rate_limited— more than 5 attempts in 15 min.
GET /auth/me
Returns the active session. Useful for the frontend to rehydrate state after refresh.
Auth: session (cookie or Authorization: Bearer <jwt>).
Response 200: AuthSessionResponse — same shape as login. access_token is cookie-session when coming from a cookie (the real JWT is in the cookie).
PATCH /auth/me/locale
Changes the logged-in user's language preference.
Request body (UserLocaleUpdateRequest):
{ "locale": "pt-BR" }
Accepted values: "pt-BR", "en", or null (clears the preference, uses the browser-detected locale).
Response 200: {"status":"ok"}.
POST /auth/logout
Revokes the current session (invalidates the jti on the server) and clears the cookie.
Auth: session.
Response 200: {"status":"logged_out"}.
POST /auth/password-reset/request
Requests a password-reset email. Always returns 200, even if the email doesn't exist — to avoid leaking registered users.
Request body (PasswordResetRequestInput):
{ "email": "admin@acme.com" }
Response 200: {"status":"ok"}.
POST /auth/password-reset/confirm
Sets a new password using the token that came in the email.
Request body (PasswordResetConfirmInput):
{
"token": "reset-token-from-email",
"new_password": "new-strong-password"
}
token— 1–256 chars.new_password— at least 10 chars.
Response 200: {"status":"ok"}.
Errors:
400 bad_request— expired or invalid token.
GET /auth/organizations/{organization_id}/members
Lists every member of the organization.
Auth: session. The user must belong to the organization.
Response 200: array of OrganizationMembershipRecord:
[
{
"id": "mem_...",
"organization_id": "org_abc123",
"user_id": "usr_...",
"email": "analyst@acme.com",
"display_name": "Analyst",
"role": "engineer",
"created_at": "...",
"updated_at": "..."
}
]
POST /auth/organizations/{organization_id}/members
Adds a member without invitation — creates the user with an already-defined password. Useful for automated provisioning.
Auth: session with member:manage permission (role org_admin).
Request body (OrganizationMemberCreateRequest):
{
"email": "analyst@acme.com",
"display_name": "New analyst",
"password": "strong-password",
"role": "engineer"
}
role∈ read_only.
Response 200: OrganizationMembershipRecord.
GET /auth/organizations/{organization_id}/invitations
Lists pending/accepted/expired invitations.
Response 200: array of OrganizationInvitationRecord.
POST /auth/organizations/{organization_id}/invitations
Sends an email invitation for a future member.
Request body (OrganizationInvitationCreateRequest):
{
"email": "guest@acme.com",
"role": "reviewer"
}
Response 200: OrganizationInvitationRecord with token, status, expires_at.
GET /auth/invitations/{token}
Look up an invitation by token (used by the public accept screen).
Auth: public.
Response 200: OrganizationInvitationRecord.
POST /auth/invitations/{token}/accept
Accepts the invitation, creates the user (if necessary) and opens a session.
Request body (OrganizationInvitationAcceptRequest):
{
"display_name": "Display Name",
"password": "strong-password"
}
display_nameis optional when the user already exists.password— at least 10 chars.
Response 200: AuthSessionResponse.
Shared structures
AuthSessionResponse
{
"access_token": "string (JWT or 'cookie-session')",
"token_type": "bearer",
"expires_at": "ISO 8601 UTC",
"user": "UserRecord",
"organizations": ["OrganizationRecord[]"],
"current_organization": "OrganizationRecord",
"memberships": ["OrganizationMembershipRecord[]"]
}
UserRecord
{
"id": "string",
"email": "string",
"display_name": "string",
"is_active": true,
"is_platform_admin": false,
"created_at": "ISO 8601",
"updated_at": "ISO 8601"
}
OrganizationRecord
{
"id": "string",
"slug": "string",
"name": "string",
"description": "string",
"role": "org_admin | security_content_lead | reviewer | engineer | read_only | null",
"is_active": true,
"created_at": "ISO 8601",
"updated_at": "ISO 8601"
}
OrganizationInvitationRecord
{
"id": "string",
"organization_id": "string",
"email": "string",
"role": "EnterpriseRole",
"token": "string",
"status": "pending | accepted | expired | linked",
"expires_at": "ISO 8601",
"created_at": "ISO 8601"
}