Skip to content

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
FieldTypeRequiredDescription
namestringYesUser display name
emailstringYesAccount email
passwordstringYesPassword, 8 to 128 characters
imagestringNoProfile image URL
callbackURLstringNoTrusted URL used when a redirect flow is enabled
rememberMebooleanNoWhether 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
FieldTypeRequiredDescription
emailstringYesAccount email
passwordstringYesAccount password
rememberMebooleanNoDefaults to true; false expires the cookie when the browser closes
callbackURLstringNoTrusted 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/json
json
{
  "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/json
json
{
  "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.

MethodPathRequest contractSuccess response
POST/auth/organization/createname, slug; optional logo, metadata, keepCurrentActiveOrganizationCreated organization
GET/auth/organization/listNo bodyArray of organizations for the user
GET/auth/organization/get-full-organizationQuery organizationId or organizationSlug; optional membersLimit; defaults to active organizationOrganization with members, teams, and invitations
POST/auth/organization/set-activeorganizationId or organizationSlug; organizationId: null clears the active organizationSelected full organization or null
POST/auth/organization/updatedata with optional name, slug, logo, or metadata; optional organizationId defaults to active organizationUpdated organization
POST/auth/organization/deleteorganizationIdDeleted organization
POST/auth/organization/invite-memberemail, role; optional organizationId, teamId, resendInvitation record
POST/auth/organization/accept-invitationinvitationIdAccepted invitation and membership result
POST/auth/organization/remove-membermemberIdOrEmail; optional organizationIdRemoved member
POST/auth/organization/update-member-rolememberId, role; optional organizationIdUpdated member
GET/auth/organization/list-membersQuery 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:

StatusMeaning
200Successful Better Auth operation
400Invalid input, token, organization operation, or organization resource lookup
401Missing session, invalid credentials, or an endpoint-specific authorization
403Authenticated user lacks a required organization permission
422Email sign-up found an existing user or failed to create the user
429Route-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.ts adapts 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/reference for inspecting the exact installed plugin contract during development.

Built with ❤️ by the Jubiloop team