Admin API (auth.admin)
Create, list, update, ban, invite and delete app users from your server code and generate sign-in, recovery and invite links: supabase's auth.admin in every SDK, with who may call it, limits and errors.
auth.admin manages your app users from code you run yourself — a back office, a support tool, a migration script, a cron job. It is supabase's admin API with the same names. It is the same work the console's Users page does, under the same checks.
import { createClient } from '@sovaria/sdk'
const admin = createClient(SOVARIA_URL, SERVER_KEY, { results: 'supabase' }) // or a team member's session
const { data: { user }, error } = await admin.auth.admin.createUser({
email: 'ada@example.com', password: 'Correct-Horse-9!', email_confirm: true,
user_metadata: { name: 'Ada' }, app_metadata: { plan: 'pro' }, roles: ['librarian'],
})
const { data: page } = await admin.auth.admin.listUsers({ page: 1, perPage: 100 }) // { users, nextPage, lastPage, total }
const { data: one } = await admin.auth.admin.getUserById(user.id)
await admin.auth.admin.updateUserById(user.id, { app_metadata: { plan: 'team' }, ban_duration: '24h' })
await admin.auth.admin.inviteUserByEmail('grace@example.com', { data: { team: 'blue' } })
// A temporary password made by the server, returned once; the user must change it at first sign-in:
const { data: first } = await admin.auth.admin.createUser({ email: 'lin@example.com', roles: ['STAFF'], temporary_password: true })
// first.temporaryPassword → hand it to Lin (never log or store it)
const { data: link } = await admin.auth.admin.generateLink({ type: 'recovery', email: 'ada@example.com' })
const { data: signup } = await admin.auth.admin.generateLink({ type: 'signup', email: 'lin@example.com', password: 'Correct-Horse-9!' })
await admin.auth.admin.deleteUser(user.id)#Every call
| Call | What it does | HTTP |
|---|---|---|
createUser({ email, password?, email_confirm?, user_metadata?, app_metadata?, roles? }) | Creates the user. Without a password they sign in by link or code. email_confirm skips the confirmation email. | POST /projects/{id}/auth/users, then PATCH …/users/{uid} for metadata |
listUsers({ page, perPage }) | Newest first, up to 500 a page and 50 pages deep (use auth.productUsers.listPage and its cursor for more). total comes with the answer. | GET /projects/{id}/auth/users |
getUserById(id) | The Users row plus user_metadata, app_metadata, custom claims and banned. | GET /projects/{id}/auth/users/{uid} |
updateUserById(id, { user_metadata?, app_metadata?, email_confirm?, roles?, ban_duration? }) | Metadata is merged (a null value removes the key); app_metadata only admins can set; ban_duration: 'none' lifts a ban, any other value bans until you lift it. | PATCH …/users/{uid}, PUT …/roles, POST …/ban |
deleteUser(id) | Deletes the user and their sessions. | DELETE /projects/{id}/auth/users/{uid} |
inviteUserByEmail(email, { data? }) | Creates a passwordless user and sends your project's sign-in email. | POST /projects/{id}/auth/users (send_magic_link) |
generateLink({ type, email, password?, options }) | Returns the link an email would carry — magiclink, recovery, invite (creates a passwordless user) or signup (creates a user with password, address not yet confirmed; the link confirms it) — with the 6-digit email_otp (sign-in links) and the hashed_token for verifyOtp({ token_hash, type }). created: true when the call created the user. Nothing is emailed. CLI: sov auth-product link --type signup --email …. | POST /projects/{id}/auth/generate-link |
mfa.listFactors({ userId }) / mfa.deleteFactors({ userId }) | A user's second factors; remove them all (they enroll again; backup codes are revoked). | GET / POST /projects/{id}/auth/users/{uid}/mfa[/reset] |
#Security and limits
- Every call checks that the caller administers this project's users; a user of another project is a
404. generateLinkis for org owners and admins, with a team member's session (API keys are refused): at most 60 links an hour per admin, and each one is written to the audit log (auth.admin.link_generated, with the type and the user). The link signs that user in — send it only to them, never log it.- Invite links last 24 hours; magic links, recovery links and signup confirmation links last as long as the emailed ones (Auth › Settings). A
signuppassword must pass the project's password policy, and the audit entry recordscreated: true(never the password). - User changes are recorded in the project history by field name (values can be personal data, so they are not).
- Email, password and phone belong to the user: they change them with
updateUser, or you send a recovery link.
#Errors
| Status | Meaning |
|---|---|
401 | No session, or an expired one. |
403 | Not an owner/admin of the organization, an app user's session, or an API key where a person is needed (generateLink). |
404 | No such user in this project (or, for magiclink/recovery, no active user with that email). |
409 | EMAIL_EXISTS: the address already has an account in this project (createUser, invite, signup). |
422 | A reserved claim, metadata that is too large, a signup link without a password (PASSWORD_REQUIRED) or one the password policy refuses. |
429 | ADMIN_LINK_RATE_LIMITED: more than 60 links in the last hour. |