Skip to content

Admin API

base.auth.admin calls /auth/v1/admin/* and works only with a secret key (lb_sec_...). Other keys and user tokens get 403 service_role_required.

import { createClient } from "@potalab/base"
const admin = createClient({ url: process.env.POTALAB_BASE_URL!, key: process.env.POTALAB_BASE_SECRET_KEY! })
await admin.auth.admin.inviteUserByEmail("ann@acme.com", {
redirectTo: "https://app.acme.com/welcome",
data: { name: "Ann" },
})
await admin.auth.admin.createUser({ email: "bob@acme.com", password: "...", email_confirm: true })
const { data } = await admin.auth.admin.listUsers({ perPage: 50 }) // { users, nextCursor, total }
await admin.auth.admin.getUserById(id)
await admin.auth.admin.updateUserById(id, { user_metadata: { plan: "pro" } })
await admin.auth.admin.deleteUser(id)
const { data } = await admin.auth.admin.generateLink({ type: "magiclink", email: "ann@acme.com" })

generateLink (invite, magiclink or recovery) returns action_link without sending an email. Deliver it yourself and treat it as a credential: do not log it. signup links are not supported, and redirectTo must pass the redirect allowlist.

An invited user is created in the invited state with no password. PotaLab Base emails a single-use link valid for 7 days with ?type=invite&token=.... Your page completes it:

await base.auth.acceptInvite({ token, password })

Accepting sets the password, marks the email verified and signs the user in. A resend invalidates the previous link; used or expired links fail with invite_invalid.

app_metadata can only be changed by admins and is passed to the claims hook, which makes it the place for roles and plans. user_metadata is profile data users can edit.

The dashboard Authentication → Users page and the Management API (auth:admin scope) also support searching, banning, sign-out everywhere, password reset emails, MFA reset and bulk import. Bans are checked on every sign-in path and on refresh (403 user_banned); access tokens issued before the ban stay valid until they expire.