Sign-in

BRM-Auth's sign-in endpoints, used by the sign-in page and by Bramble's own apps. Passwords are sent as the SHA3-512 hex digest of the password (the sign-in page hashes it in the browser; Bramble stores an scrypt hash of that). A successful sign-in sets the sso_session and session_id cookies. Failed attempts are limited to 10 per account per 15 minutes.

MethodPathSummary
POST/auth/mail/verifyCheck a mail sign-in (mail server only)
POST/auth/mail/tokenMail app sign-in (Bramble Mail only)
POST/auth/loginSign in
POST/auth/mfa/setup-generateStart required two-step setup during sign-in
POST/auth/mfa/setup-verifyFinish required two-step setup and sign in
POST/auth/forgot-passwordSend a password reset email
POST/auth/reset-passwordSet a new password from a reset link
POST/auth/register/check-slugPreview an organisation's address
POST/auth/find-workspaceFind your organisation's address
POST/auth/registerSign up a new organisation
GET/auth/verify-emailConfirm an email address from a sign-up link
POST/auth/refreshRenew the short-lived session token
GET/auth/validateIs this session still valid?
GET/auth/bridgeCarry a sign-in over to the organisation's address
POST/auth/logoutSign out
POST/auth/logout-allSign out everywhere
GET/auth/checkWho is signed in?

#Check a mail sign-in (mail server only)

POST/auth/mail/verify

Used by Bramble's mail server to check IMAP, SMTP, CalDAV and CardDAV sign-ins. Needs the mail hook key.

Address
https://<your-org>.brambleit.cloud/auth/mail/verify
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
userany
passwordany
serviceany
ripany
targetany

Response 200

FieldTypeDescription
okboolean
userany
Example response
JSON
{
  "ok": true,
  "user": null
}

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/mail/verify', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"user":"…","password":"…","service":"…","rip":"…","target":"…"}),
});
const data = await res.json();

#Mail app sign-in (Bramble Mail only)

POST/auth/mail/token

For BRM-MailApp only (X-Mail-App-Key), with the person's own session_id cookie forwarded. Returns { token, expires_at, mailbox } where mailbox is the address to sign in as.

Address
https://<your-org>.brambleit.cloud/auth/mail/token
Requires
None: public, or protected by a token in the address or body.
Headers
x-mail-app-key

Response 200

FieldTypeDescription
tokenstring
expires_atstring
mailboxany
emailstring
Example response
JSON
{
  "token": "…",
  "expires_at": "string",
  "mailbox": null,
  "email": "alex@example.com"
}

Errors

StatusMessage
403Forbidden
401Not signed in
403No licence that includes Bramble Mail.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/mail/token', {
  method: 'POST',
  credentials: 'include',
});
const data = await res.json();

#Sign in

POST/auth/login

Checks the password (and two-step code). On success sets the session cookies and returns where to go next. If two-step sign-in is on and totp is missing, answers 400 TOTP code is required; send the same request again with the code. If a security policy requires two-step sign-in and it isn't set up yet, returns { mfaSetupRequired: true, setupToken } instead.

Address
https://<your-org>.brambleit.cloud/auth/login
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
emailrequiredstringYour username (email address).
shaPasswordrequiredanySHA3-512 hex digest of the password.
totprequiredanyThe 6-digit authenticator code, when two-step sign-in is on.
redirectanyWhere to go after signing in.
ipanyYour IP address as the page sees it (recorded in the sign-in log).
browseranyBrowser name, for the sign-in log.
platformanyOperating system, for the sign-in log.
userAgentanyUser agent, for the sign-in log.
login_countryanyCountry, for the sign-in log.
login_regionanyRegion, for the sign-in log.
login_cityanyCity, for the sign-in log.

Response 200

FieldTypeDescription
mfaSetupRequiredboolean
setupTokenstring
Example response
JSON
{
  "mfaSetupRequired": false,
  "setupToken": "…"
}

Errors

StatusMessage
400TOTP code is required
401Invalid email or password
401Invalid TOTP code
401Sign in is blocked. If you just registered, please verify your email first; otherwise contact your IT Administrator.
403Sign-in blocked by your organisation's security policy.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/login', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"email":"alex@example.com","shaPassword":"…","totp":"…","redirect":"…","ip":"…","browser":"…","platform":"…","userAgent":"…","login_country":"…","login_region":"…","login_city":"…"}),
});
const data = await res.json();

#Start required two-step setup during sign-in

POST/auth/mfa/setup-generate
Address
https://<your-org>.brambleit.cloud/auth/mfa/setup-generate
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
setupTokenstringThe setupToken from POST /auth/login.

Response 200

FieldTypeDescription
qrDataUrlstring
secretany
Example response
JSON
{
  "qrDataUrl": "https://example.com/…",
  "secret": null
}

Errors

StatusMessage
400MFA setup is not required for this account
401Invalid or expired setup session
404User not found
403Sign-in blocked by your organisation's security policy.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/mfa/setup-generate', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"setupToken":"…"}),
});
const data = await res.json();

#Finish required two-step setup and sign in

POST/auth/mfa/setup-verify
Address
https://<your-org>.brambleit.cloud/auth/mfa/setup-verify
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
setupTokenstringThe setupToken from POST /auth/login.
coderequiredanyThe 6-digit code from the authenticator app.
ipany
browserany
platformany
userAgentany
login_countryany
login_regionany
login_cityany
redirectany

Response 200

Errors

StatusMessage
400Verification code is required
400MFA setup is not required for this account
401Invalid or expired setup session
404User not found
403Sign-in blocked by your organisation's security policy.
401Invalid TOTP code

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/mfa/setup-verify', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"setupToken":"…","code":"…","ip":"…","browser":"…","platform":"…","userAgent":"…","login_country":"…","login_region":"…","login_city":"…","redirect":"…"}),
});
const data = await res.json();

#Send a password reset email

POST/auth/forgot-password

Always answers the same way, whether or not the address exists. Limited to 5 per address per hour.

Address
https://<your-org>.brambleit.cloud/auth/forgot-password
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
emailrequiredstring

Response 200

Errors

StatusMessage
400Email is required

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/forgot-password', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"email":"alex@example.com"}),
});
const data = await res.json();

#Set a new password from a reset link

POST/auth/reset-password
Address
https://<your-org>.brambleit.cloud/auth/reset-password
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
tokenrequiredstringThe token from the reset email.
shaPasswordrequiredanySHA3-512 hex digest of the new password.

Response 200

FieldTypeDescription
messagestring
Example response
JSON
{
  "message": "Saved"
}

Errors

StatusMessage
400Token and new password are required
400This reset link is invalid or has expired.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/reset-password', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"token":"…","shaPassword":"…"}),
});
const data = await res.json();

#Preview an organisation's address

POST/auth/register/check-slug

While someone types their company name at sign-up, shows the <slug>.brambleit.cloud address it would get.

Address
https://<your-org>.brambleit.cloud/auth/register/check-slug
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
company_namestring

Response 200

FieldTypeDescription
domainany
messagestring
Example response
JSON
{
  "domain": null,
  "message": "Saved"
}

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/register/check-slug', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"company_name":"Example company name"}),
});
const data = await res.json();

#Find your organisation's address

POST/auth/find-workspace

Emails the person the sign-in address(es) for their account. Used on the shared sign-in page.

Address
https://<your-org>.brambleit.cloud/auth/find-workspace
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
emailstring

Response 200

FieldTypeDescription
domainstring
Example response
JSON
{
  "domain": "string"
}

Errors

StatusMessage
400Enter a valid email address
404We couldn't find a workspace for that email.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/find-workspace', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"email":"alex@example.com"}),
});
const data = await res.json();

#Sign up a new organisation

POST/auth/register
Address
https://<your-org>.brambleit.cloud/auth/register
Requires
None: public, or protected by a token in the address or body.

Request body JSON

NameTypeDescription
company_namerequiredstring
org_typestring
first_namerequiredstring
last_namerequiredstring
recovery_emailrequiredstring
shaPasswordrequiredany
terms_acceptedrequiredany

Response 201

FieldTypeDescription
messagestring
emailstring
domainstring
Example response
JSON
{
  "message": "Saved",
  "email": "alex@example.com",
  "domain": "string"
}

Errors

StatusMessage
400Company name is required
400First and last name are required
400A valid email address is required
400Password is required
400You must accept the Terms of Service

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/register', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"company_name":"Example company name","org_type":"string","first_name":"Alex","last_name":"Morgan","recovery_email":"alex@example.com","shaPassword":"…","terms_accepted":"…"}),
});
const data = await res.json();

#Confirm an email address from a sign-up link

GET/auth/verify-email
Address
https://<your-org>.brambleit.cloud/auth/verify-email
Requires
None: public, or protected by a token in the address or body.

Query parameters

NameTypeDescription
tokenstring

Response

A redirect.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/verify-email', {
  method: 'GET',
  credentials: 'include',
});
const data = await res.json();

#Renew the short-lived session token

POST/auth/refresh

Uses the session_id cookie to issue a new sso_session. Apps call this automatically.

Address
https://<your-org>.brambleit.cloud/auth/refresh
Requires
None: public, or protected by a token in the address or body.

Response 200

FieldTypeDescription
accessTokenstring
Example response
JSON
{
  "accessToken": "…"
}

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/refresh', {
  method: 'POST',
  credentials: 'include',
});
const data = await res.json();

#Is this session still valid?

GET/auth/validate
Address
https://<your-org>.brambleit.cloud/auth/validate
Requires
None: public, or protected by a token in the address or body.

Response 200

FieldTypeDescription
user_typestring
client_idinteger
Example response
JSON
{
  "user_type": "string",
  "client_id": 7
}

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/validate', {
  method: 'GET',
  credentials: 'include',
});
const data = await res.json();

#Carry a sign-in over to the organisation's address

GET/auth/bridge

Reached via <tenant>.brambleit.cloud/auth/bridge (proxied to this service by a Pangolin target) right after a normal login on sso.cloud.brambleit.com — see the redirect logic in issueSession() above for why this hop exists. One-time, short-lived: re-issues the same session as a cookie scoped to whichever tenant domain this request actually arrived on, then continues to the originally-requested page.

Address
https://<your-org>.brambleit.cloud/auth/bridge
Requires
None: public, or protected by a token in the address or body.

Query parameters

NameTypeDescription
ottany
nextany

Response

A redirect.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/bridge', {
  method: 'GET',
  credentials: 'include',
});
const data = await res.json();

#Sign out

POST/auth/logout
Address
https://<your-org>.brambleit.cloud/auth/logout
Requires
None: public, or protected by a token in the address or body.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/logout', {
  method: 'POST',
  credentials: 'include',
});
const data = await res.json();

#Sign out everywhere

POST/auth/logout-all

Nothing on this router ever sets req.user, so the old req.user.id here threw on every call — resolve the user from the session cookie instead, same as /logout.

Address
https://<your-org>.brambleit.cloud/auth/logout-all
Requires
None: public, or protected by a token in the address or body.

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/logout-all', {
  method: 'POST',
  credentials: 'include',
});
const data = await res.json();

#Who is signed in?

GET/auth/check
Address
https://<your-org>.brambleit.cloud/auth/check
Requires
None: public, or protected by a token in the address or body.

Query parameters

NameTypeDescription
redirectstring

Response 200

FieldTypeDescription
userIdinteger
redirectstring
Example response
JSON
{
  "userId": 1,
  "redirect": "string"
}

Errors

StatusMessage
401Not signed in
401Session expired

Example request

JavaScript
// From a page served by Bramble (uses the signed-in session)
const res = await fetch('/auth/check', {
  method: 'GET',
  credentials: 'include',
});
const data = await res.json();