Register
Create a new organisation and owner account.
Endpoint
POST /v1/auth/registerDescription
Registers a new organisation with an owner account. This is the first step for new customers to onboard to Cerberus IAM. The endpoint creates:
- A new organisation with an auto-generated slug from the organisation name
- An "Owner" role with full permissions
- An owner user account with hashed password
- Email verification token (sent via email)
The organisation is created with a "trial" status and default session settings. The organisation slug is automatically generated from the organisation name as a URL-safe identifier (e.g., "Acme Corporation" becomes "acme-corporation").
Authentication
Required: No (public endpoint)
Headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | Must be application/json |
Note: X-Org-Domain header is NOT required for registration (organisation doesn't exist yet).
Request Body
| Field | Type | Required | Description | Constraints |
|---|---|---|---|---|
organisationName | string | Yes | Display name of the organisation (slug auto-generated) | Minimum 1 character |
email | string | Yes | Owner's email address | Valid email format |
firstName | string | Yes | Owner's first name | Minimum 1 character |
lastName | string | Yes | Owner's last name | Minimum 1 character |
password | string | Yes | Owner's password | Minimum 8 characters, must meet strength requirements |
Note: The organisation slug is automatically generated from organisationName. For example:
- "Acme Corporation" becomes "acme-corporation"
- "My Company!" becomes "my-company"
- If the slug already exists, a number is appended (e.g., "acme-corporation-1")
Password Strength Requirements
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
- At least one special character (recommended but may not be enforced)
Example Request
{
"organisationName": "Acme Corporation",
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"password": "SecurePass123!"
}Response
Success Response
Status Code: 201 Created
{
"message": "Organisation and owner account created successfully",
"organisation": {
"id": "org_a1b2c3d4e5f6",
"slug": "acme-corporation",
"name": "Acme Corporation"
},
"user": {
"id": "usr_x1y2z3a4b5c6",
"email": "[email protected]",
"name": "John Doe"
}
}Note: The slug field in the response is the auto-generated slug from the organisation name.
Error Responses
400 Bad Request - Invalid Input
Missing required field:
{
"type": "https://api.cerberus-iam.com/errors/bad-request",
"title": "Bad Request",
"status": 400,
"detail": "Invalid input",
"errors": [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": ["email"],
"message": "Required"
}
]
}Password too weak:
{
"type": "https://api.cerberus-iam.com/errors/bad-request",
"title": "Bad Request",
"status": 400,
"detail": "Password too weak",
"errors": [
"Password must be at least 8 characters",
"Password must contain at least one uppercase letter",
"Password must contain at least one number"
]
}409 Conflict - Resource Already Exists
Email already registered:
{
"type": "https://api.cerberus-iam.com/errors/conflict",
"title": "Conflict",
"status": 409,
"detail": "Email already registered"
}429 Too Many Requests
Rate limit exceeded:
{
"type": "https://api.cerberus-iam.com/errors/rate-limit",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Please try again later."
}Rate limits: 30 requests per 60 seconds (authentication endpoint limit)
500 Internal Server Error
Signup failed:
{
"type": "https://api.cerberus-iam.com/errors/signup-failed",
"title": "Signup Failed",
"status": 500,
"detail": "Failed to create account"
}Side Effects
Organisation created with:
- Status:
trial - Session lifetime: 3600 seconds (1 hour)
- Session idle timeout: 1800 seconds (30 minutes)
- MFA requirement:
false(optional by default)
- Status:
Owner role created with:
- Name: "Owner"
- Slug: "owner"
- Broad administrative permissions (full CRUD over users, organisations, teams, invitations, plus read access to roles/permissions)
Organisation slug generated from the organisation name:
- Converted to lowercase
- Special characters removed
- Spaces replaced with hyphens
- Uniqueness ensured (numbers appended if needed)
User account created with:
- Password hashed using Argon2id
- Identity provider:
local - Email verification status: unverified
Email verification token generated and sent to the provided email address
Audit log entry created (if audit logging is enabled)
Next Steps
After successful registration:
- Verify email: Check email inbox for verification link
- Click verification link: Completes email verification (see verify-email.md)
- Login: Use credentials to login (see login.md)
Code Examples
cURL
curl -X POST http://localhost:4000/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"organisationName": "Acme Corporation",
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"password": "SecurePass123!"
}'JavaScript (fetch)
const response = await fetch('http://localhost:4000/v1/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
organisationName: 'Acme Corporation',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
password: 'SecurePass123!',
}),
});
if (!response.ok) {
const error = await response.json();
console.error('Registration failed:', error);
throw new Error(error.detail);
}
const data = await response.json();
console.log('Registration successful:', data);TypeScript (with error handling)
interface RegisterRequest {
organisationName: string;
email: string;
firstName: string;
lastName: string;
password: string;
}
interface RegisterResponse {
message: string;
organisation: {
id: string;
slug: string;
name: string;
};
user: {
id: string;
email: string;
name: string;
};
}
interface ProblemDetails {
type: string;
title: string;
status: number;
detail: string;
errors?: any[];
}
async function register(data: RegisterRequest): Promise<RegisterResponse> {
const response = await fetch('http://localhost:4000/v1/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
const problem: ProblemDetails = await response.json();
// Handle specific errors
if (problem.status === 409) {
throw new Error('This email is already registered');
}
if (problem.status === 400 && problem.errors) {
// Handle validation errors
const errorMessages = problem.errors.map((e) => e.message).join(', ');
throw new Error(`Validation failed: ${errorMessages}`);
}
throw new Error(problem.detail || 'Registration failed');
}
return response.json();
}
// Usage
try {
const result = await register({
organisationName: 'Acme Corporation',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
password: 'SecurePass123!',
});
console.log('Success:', result);
// Redirect to email verification page or login
} catch (error) {
console.error('Registration failed:', error.message);
// Show error to user
}Security Considerations
- Password Hashing: Passwords are hashed using Argon2id before storage
- Rate Limiting: Endpoint is rate-limited to prevent abuse
- Email Verification: Users must verify their email before full access
- Slug Auto-generation: Organisation slugs are automatically generated and validated as URL-safe
- Slug Uniqueness: Organisation slugs must be globally unique (ensured automatically)
- Input Validation: All inputs are validated using Zod schemas
- SQL Injection Protection: Prisma ORM prevents SQL injection
- HTTPS Required: Always use HTTPS in production
Related Endpoints
- POST /v1/auth/login - Login with credentials
- GET /v1/auth/verify-email - Verify email address
- POST /v1/auth/forgot-password - Request password reset