Authentication¶
The Coaching App uses Better-auth for all authentication. Sessions are cookie-based — a better-auth.session_token cookie is set on sign-in and must be included in subsequent requests.
All auth routes are handled by Better-auth under /api/auth/*.
Endpoints¶
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/auth/sign-up/email |
Public | Create a new user account |
POST |
/api/auth/sign-in/email |
Public | Sign in and start a session |
POST |
/api/auth/sign-out |
Required | End the current session |
GET |
/api/auth/session |
Optional | Get the current session |
POST |
/api/auth/forget-password |
Public | Send a password reset email |
POST |
/api/auth/reset-password |
Public | Reset password using a token |
POST |
/api/auth/resend-activation |
Public | Resend account activation link with cooldown |
POST /api/auth/sign-up/email¶
Create a new user account with email and password. Newly created accounts have no organization role until they accept an invite or are manually assigned.
Request body:
{
"email": "user@example.com",
"password": "Secret1234!",
"name": "Jane Smith",
"preferredLanguage": "es"
}
| Field | Type | Required | Constraints |
|---|---|---|---|
email |
string | Yes | Must be a valid email address |
password |
string | Yes | Minimum 8 characters |
name |
string | Yes | Full display name |
preferredLanguage |
string | No | en or es. If omitted, backend uses Accept-Language header or defaults to en |
Language persistence on signup
During signup, the backend persists the resolved language in profiles.preferred_language so future transactional emails (password reset, activation resend, invites) can be localized.
Response 200:
{
"user": {
"id": "abc123",
"email": "user@example.com",
"name": "Jane Smith",
"createdAt": "2025-01-01T00:00:00.000Z"
}
}
Error responses:
| Code | Condition |
|---|---|
400 |
Missing fields or password too short |
409 |
Email already registered |
POST /api/auth/sign-in/email¶
Sign in with email and password. Sets a better-auth.session_token cookie on the response.
Request body:
| Field | Type | Required |
|---|---|---|
email |
string | Yes |
password |
string | Yes |
Response 200:
Error responses:
| Code | Condition |
|---|---|
401 |
Invalid email or password |
POST /api/auth/sign-out¶
End the current session. Clears the session cookie.
Request: No body required. Session cookie must be present.
Response 200:
GET /api/auth/session¶
Returns information about the currently active session. Returns null session if not authenticated.
Response 200 (authenticated):
{
"session": {
"id": "...",
"userId": "abc123",
"expiresAt": "2025-02-01T00:00:00.000Z"
},
"user": {
"id": "abc123",
"email": "user@example.com",
"name": "Jane Smith"
}
}
Response 200 (not authenticated):
POST /api/auth/forget-password¶
Send a password reset email to the specified address. The email contains a time-limited reset link.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Email address to send the reset link to |
redirectTo |
string | No | URL the reset link will redirect to after clicking |
Response 200:
Always returns 200
This endpoint always returns 200 regardless of whether the email exists in the system, to prevent email enumeration.
Email localization
Password reset emails are localized using this fallback chain: callbackURL language (if present) -> profile preferred_language -> en.
POST /api/auth/reset-password¶
Reset a user's password using the token from a reset email.
Request body:
| Field | Type | Required | Constraints |
|---|---|---|---|
token |
string | Yes | Token from the password reset email |
newPassword |
string | Yes | Minimum 8 characters |
Response 200:
Error responses:
| Code | Condition |
|---|---|
400 |
Invalid or expired token, or password too short |
POST /api/auth/resend-activation¶
Resend an account activation email for the provided address.
This endpoint is designed for the post-signup "I didn't receive the email" flow and includes a per-email cooldown.
Cooldown is configured with RESEND_ACTIVATION_COOLDOWN_SECONDS on the backend. If unset or invalid, it falls back to the recommended default of 60 seconds.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Account email address |
callbackURL |
string | No | URL to redirect to after verification |
Response 200:
Error responses:
| Code | Condition |
|---|---|
400 |
Invalid email format |
429 |
Cooldown active for this email (retryAfterSeconds included) |
Enumeration-safe behavior
For validly formatted emails, the endpoint does not reveal whether the account exists. It attempts to dispatch a verification email through Better-auth and returns success metadata unless the cooldown is active.
Verification callback behavior
The verification link ultimately redirects to /email-verification. On mobile, if the app is installed and universal links are configured, this route opens in-app and shows a success/failure state (error query param) before the user continues to login.
Activation email localization
Activation emails are localized using callback URL language first (for example /email-verification?lang=es), then profile preferred_language, then en.
POST /api/users/me/password¶
Change the current user's own password. Requires knowing the current password.
Authentication: Required.
Request body:
| Field | Type | Required | Constraints |
|---|---|---|---|
currentPassword |
string | Yes | The user's existing password |
newPassword |
string | Yes | Minimum 8 characters |
Response 200:
Error responses:
| Code | Condition |
|---|---|
400 |
Missing fields, new password too short, or current password incorrect |