Skip to content
esc

Search pages and sections. Try a tool name, an error code, a route.

↑↓ navigate↵ openesc close

SDK · @manybases/js

@manybases/js — auth

Sign people up and in, keep their session, and let them out. Every method returns { data, error } and never throws.

ts
const { data, error } = await mb.auth.signup({ email, password });
if (error) console.error(error.code, error.hint);
MethodCallsReturns
signup({ email, password })POST /v1/auth/signup{ user, session: { token }, seats } — persists the session
login({ email, password })POST /v1/auth/loginthe same — persists the session
logout()POST /v1/auth/logout{ logged_out: true } — always clears the local session, even on a refusal
me()GET /v1/auth/me{ user, seats } — clears the local session on a 401, and only there
sendMagicLink({ email })POST /v1/auth/magic-link{ sent: true }
verifyMagicLink(token)GET /v1/auth/magic-link/verify{ user, session: { token }, seats } — persists the session
verifyResetToken(token)GET /v1/auth/reset-password/:token{ token } — checks the link without consuming it
resetPassword({ token, password })POST /v1/auth/reset-password{ reset: true } — consumes the link and signs the person out everywhere
getSession()—the stored token, or null
setSession(token | null)—stores a token, or clears it

user is { id, email, created_at }; seats is { used, cap }, with cap: null when the plan's cap could not be read.

Where the session lives#

By default the token is kept in localStorage, for 30 rolling days. Any injected script can read it: your Content Security Policy protects your session, the SDK cannot. Pass storage at createClient to use sessionStorage, an in-memory store or your own vault, or storage: null to keep the token in memory only. On the server, keep the token wherever your framework keeps a user's state and hand it to a per-request client with setSession.

Only three calls persist a session — signup, login, verifyMagicLink. A data-plane 401 does not clear it: a refused read does not mean the session is dead.

What the API decides#

  • A seat is taken at sign-up: seats.used in a sign-up response counts the person who just signed up. At the cap (3 on Free, 1 000 on Builder and Studio) signup answers 402 quota_exceeded and creates nothing; login is never refused by the cap.
  • An e-mail already registered answers 400 validation_error; wrong credentials answer 401.
  • Every publishable key signs users up, whoever created it — an agent's included. (Until 2026-09-07 a key created by an agent alone was inert, and signup answered 401; that rule is removed.) See Invite users.
  • A magic-link or reset token that is unknown, expired or already used answers the same 401.
  • sendMagicLink answers 429 rate_limited beyond 20 requests an hour from one IP, or once today's sign-in emails are used up (50 per app, 70 across all apps, until 00:00 UTC); retryAfter gives the delay. Beyond 5 requests an hour for one address it still answers { sent: true }, and nothing is sent.
  • verifyMagicLink answers 402 quota_exceeded when it would sign up a new person on an app whose seat cap is reached, or on an app in read-only mode. For an address already signed up it is a sign-in, and the cap never refuses it.

Invitation links are minted server-side, by a signed-in user (POST /v1/invites), and lead to your own sign-up page: the SDK has no method for them, the link's token is consumed by your signup call.