Skip to main content

Email Registration

POST /api/client/accounts/emails/register

Register a new account with email and password

Request Body

name
string
required
User’s full name (1-100 characters, trimmed)
email
string
required
Valid email address
password
string
required
Password meeting the following requirements:
  • Minimum 8 characters
  • Maximum 128 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one number

Request Schema

export const emailRegisterInputSchema = z.object({
  name: z
    .string({ error: 'Name is required' })
    .min(1, { message: 'Name is required' })
    .max(100, { message: 'Name must be at most 100 characters' })
    .transform((name) => name.trim()),
  email: z.string({ error: 'Email is required' }).email({
    message: 'Invalid email address',
  }),
  password: passwordSchema,
});

Response

type
string
Either "success" or "verify"

Success Response (type: “success”)

account
object
workspaces
array
Array of workspace objects the user has access to
deviceId
string
Unique device identifier
token
string
JWT authentication token

Verify Response (type: “verify”)

id
string
OTP verification ID
expiresAt
date
OTP expiration timestamp

Error Codes

  • 400 - EmailAlreadyExists: Email already registered
  • 400 - AccountCreationFailed: Failed to create account
  • 400 - AccountPendingVerification: Account requires verification
  • 429 - TooManyRequests: Rate limit exceeded

Email Login

POST /api/client/accounts/emails/login

Authenticate with email and password

Request Body

email
string
required
Email address
password
string
required
Account password

Request Schema

export const emailLoginInputSchema = z.object({
  email: z.string({ error: 'Email is required' }).email({
    message: 'Invalid email address',
  }),
  password: z.string({ error: 'Password is required' }),
});

Response

Same as registration - returns discriminated union of success or verify type.

Error Codes

  • 400 - EmailOrPasswordIncorrect: Invalid credentials
  • 400 - AccountPendingVerification: Account not verified
  • 429 - TooManyRequests: Rate limit exceeded

Google Login

POST /api/client/accounts/google/login

Authenticate with Google OAuth

Request Body

code
string
required
Google OAuth authorization code

Request Schema

export const googleLoginInputSchema = z.object({
  code: z.string(),
});

Response

Same as email login - returns discriminated union of success or verify type.

Behavior

  • If account exists with email: Updates Google ID and activates if email verified
  • If account doesn’t exist: Creates new account with Google profile data
  • Downloads and stores Google profile picture as avatar (500x500px JPEG)
  • Only allows images from trusted Google domains

Error Codes

  • 400 - GoogleAuthFailed: Google authentication failed
  • 400 - AccountCreationFailed: Failed to create account

Email Verification

POST /api/client/accounts/emails/verify

Verify email with OTP code

Request Body

id
string
required
OTP verification ID (from registration response)
otp
string
required
One-time password code

Request Schema

export const emailVerifyInputSchema = z.object({
  id: z.string(),
  otp: z.string(),
});

Response

Returns LoginOutput with account and authentication token.

Error Codes

  • 400 - AccountOtpInvalid: Invalid or expired OTP
  • 404 - AccountNotFound: Account not found

Password Reset (Initiate)

POST /api/client/accounts/emails/passwords/reset/init

Request password reset OTP

Request Body

email
string
required
Account email address

Request Schema

export const emailPasswordResetInitInputSchema = z.object({
  email: z.string().email(),
});

Response

id
string
OTP verification ID
expiresAt
date
OTP expiration timestamp

Behavior

  • Always returns 200 even if email doesn’t exist (security best practice)
  • Sends OTP email if account exists
  • OTP expires based on server configuration

Error Codes

  • 429 - TooManyRequests: Rate limit exceeded

Password Reset (Complete)

POST /api/client/accounts/emails/passwords/reset/complete

Complete password reset with OTP and new password

Request Body

id
string
required
OTP verification ID
otp
string
required
One-time password code
password
string
required
New password (must meet password requirements)

Request Schema

export const emailPasswordResetCompleteInputSchema = z.object({
  id: z.string(),
  otp: z.string(),
  password: passwordSchema, // Same validation as registration
});

Response

success
boolean
Always true on successful reset

Behavior

  • Updates account password
  • Logs out all devices for security
  • User must log in again with new password

Error Codes

  • 400 - AccountOtpInvalid: Invalid or expired OTP
  • 400 - AccountOtpInvalid: Account registration incomplete

Logout

DELETE /api/client/accounts/logout

Logout current device

Authentication

Requires valid JWT token in Authorization header.

Response

Returns empty object {}.

Behavior

  • Deletes current device from database
  • Publishes device.deleted event
  • Invalidates JWT token for this device

Account Sync

POST /api/client/accounts/sync

Sync account data and workspaces

Authentication

Requires valid JWT token in Authorization header.

Response

account
object
workspaces
array
Array of workspace objects where user has active role

Response Schema

export const accountSyncOutputSchema = z.object({
  account: accountOutputSchema,
  workspaces: z.array(workspaceOutputSchema),
  token: z.string().optional(),
});

Behavior

  • Updates device metadata (last sync time, IP, platform, version)
  • Returns all active workspaces where user role is not none
  • Filters out UserStatus.Removed users

Error Codes

  • 404 - AccountNotFound: Account not found
  • 404 - DeviceNotFound: Device not found
  • 401 - Unauthorized: Invalid or expired token

Build docs developers (and LLMs) love