Appearance
Authentication API
Jubiloop mounts Better Auth at /auth/*. This page documents the authentication and organization contracts used by Jubiloop clients. Better Auth owns these payloads, so they do not use Jubiloop's data/messages/errors response envelope.
Base URLs
- Local:
https://api.jubiloop.localhost/auth - Development:
https://dev-api.jubiloop.ca/auth - QA:
https://qa-api.jubiloop.ca/auth - Production:
https://api.jubiloop.ca/auth
Requests that create or use a session must allow credentials so the browser can store and send the HTTP-only jubiloop-prefixed session cookie. Sessions expire after seven days and become eligible for refresh after one day. Email verification and social sign-in are not enabled.
Common Shapes
Successful sign-up and sign-in responses contain a token and user. Sign-in also returns redirect and an optional url. The server sets the session cookie; browser clients should use the cookie rather than persisting the returned token.
json
{
"token": "opaque-session-token",
"user": {
"id": "0198...",
"name": "Jamie Doe",
"email": "jamie@example.com",
"emailVerified": false,
"image": null,
"createdAt": "2026-09-05T12:00:00.000Z",
"updatedAt": "2026-09-05T12:00:00.000Z"
}
}Better Auth errors use a single error object rather than Jubiloop's errors array:
json
{
"code": "INVALID_EMAIL_OR_PASSWORD",
"message": "Invalid email or password"
}Clients must branch on the HTTP status and code, not on the human-readable message. A throttled request returns 429 Too Many Requests.
If the Adonis-to-Better-Auth adapter itself throws unexpectedly, the proxy returns 500 with { "error": "Internal authentication error", "code": "AUTH_FAILURE" }. This adapter failure is distinct from a Better Auth endpoint error.
Core Endpoints
Sign Up
http
POST /auth/sign-up/email
Content-Type: application/json| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | User display name |
email | string | Yes | Account email |
password | string | Yes | Password, 8 to 128 characters |
image | string | No | Profile image URL |
callbackURL | string | No | Trusted URL used when a redirect flow is enabled |
rememberMe | boolean | No | Whether to create a persistent browser session |
Returns 200 OK with the token/user shape above and a session cookie. Invalid email, password, or request fields return 400 Bad Request. An existing email returns 422 Unprocessable Entity with code USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL.
Sign In
http
POST /auth/sign-in/email
Content-Type: application/json| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Account email |
password | string | Yes | Account password |
rememberMe | boolean | No | Defaults to true; false expires the cookie when the browser closes |
callbackURL | string | No | Trusted URL used when a redirect flow is enabled |
Returns 200 OK with { "redirect": false, "token": "...", "user": {...} } and a session cookie. Invalid credentials return 401 Unauthorized with a Better Auth error object.
Get Session
http
GET /auth/get-session
Cookie: <session cookie>Returns 200 OK with { "session": {...}, "user": {...} } for an authenticated request, or JSON null when there is no valid session. The session object includes its ID, user ID, token, expiry and timestamps, optional IP/user-agent values, and active organization/team IDs.
Sign Out
http
POST /auth/sign-out
Cookie: <session cookie>Returns 200 OK with { "success": true } and expires the current session cookie. If no session cookie is present, the endpoint still returns success.
Password Reset
Request Reset
http
POST /auth/request-password-reset
Content-Type: application/jsonjson
{
"email": "jamie@example.com",
"redirectTo": "https://app.jubiloop.ca/reset-password"
}email is required. redirectTo is optional and must be a trusted origin. A successful request returns 200 OK with { "status": true, "message": "..." } whether or not the account exists. This prevents account enumeration. The configured callback attempts to send a one-hour reset link.
Set New Password
http
POST /auth/reset-password
Content-Type: application/jsonjson
{
"newPassword": "NewPassword1!",
"token": "reset-token"
}Both fields are required. The password must be 8 to 128 characters. Returns 200 OK with { "status": true }. An invalid or expired token returns 400 Bad Request with a Better Auth error object.
Organization Endpoints
All organization operations require a valid session. When organizationId is optional, Better Auth uses the session's active organization.
| Method | Path | Request contract | Success response |
|---|---|---|---|
POST | /auth/organization/create | name, slug; optional logo, metadata, keepCurrentActiveOrganization | Created organization |
GET | /auth/organization/list | No body | Array of organizations for the user |
GET | /auth/organization/get-full-organization | Query organizationId or organizationSlug; optional membersLimit; defaults to active organization | Organization with members, teams, and invitations |
POST | /auth/organization/set-active | organizationId or organizationSlug; organizationId: null clears the active organization | Selected full organization or null |
POST | /auth/organization/update | data with optional name, slug, logo, or metadata; optional organizationId defaults to active organization | Updated organization |
POST | /auth/organization/delete | organizationId | Deleted organization |
POST | /auth/organization/invite-member | email, role; optional organizationId, teamId, resend | Invitation record |
POST | /auth/organization/accept-invitation | invitationId | Accepted invitation and membership result |
POST | /auth/organization/remove-member | memberIdOrEmail; optional organizationId | Removed member |
POST | /auth/organization/update-member-role | memberId, role; optional organizationId | Updated member |
GET | /auth/organization/list-members | Query may include organization ID or slug, pagination, sorting, and filterField, filterValue, filterOperator | { members, total } |
For list-members, the complete optional query fields are organizationId, organizationSlug, limit, offset, sortBy, sortDirection (asc or desc), filterField, filterValue, and filterOperator. If neither organization identifier is supplied, the active organization is used.
Organization roles are owner, admin, and member. Jubiloop limits each user to five created organizations and each organization to 100 members. The organization backend guide documents the business and authorization rules; the organization data model documents persistence.
Better Auth's organization plugin also exposes team-management routes because teams are enabled. Before calling a plugin route not listed above, inspect the generated non-production OpenAPI schema for the installed Better Auth version instead of assuming a payload from another release.
Status Codes
Better Auth assigns statuses per endpoint. The common statuses used by the contracts above are:
| Status | Meaning |
|---|---|
200 | Successful Better Auth operation |
400 | Invalid input, token, organization operation, or organization resource lookup |
401 | Missing session, invalid credentials, or an endpoint-specific authorization |
403 | Authenticated user lacks a required organization permission |
422 | Email sign-up found an existing user or failed to create the user |
429 | Route-level or Better Auth rate limit exceeded |
Use the endpoint's returned code with its HTTP status. Do not infer one status for every not-found or validation condition across Better Auth routes.
Implementation Notes
apps/server/app/controllers/better_auth_controller.tsadapts Adonis requests and responses to Better Auth's Web API handler.- PostgreSQL stores users, credential accounts, and organization records. Runtime sessions and password-reset verification values use Redis under the current configuration.
- Passwords are hashed with the configured Argon2 functions.
- Better Auth's OpenAPI plugin is enabled outside production at
/auth/referencefor inspecting the exact installed plugin contract during development.