HTTP API: users and auth settings
Manage a project's app users (create, import, ban, delete, roles, MFA reset) and its sign-in settings: providers, sessions, SMTP, email templates, hooks, OIDC and OAuth apps.
Manage the project's app users and how they sign in. Call these on the project URL with a team member's access token; the member's role must allow the action (owners and admins for users). Guides: Users and app roles, Authentication settings reference, Emails: SMTP, templates and limits, Auth event hooks.
URL=https://<project-ref>.sovarias.in # the project URL names the project
ANON_KEY=<anon key>
ACCESS_TOKEN=<a team member's access token>| Base URL | https://<project-ref>.sovarias.in — your project URL from Connect. |
| Keys | Send the project's key as apikey. Calls made for a signed-in user also send Authorization: Bearer <access token>. |
| Errors | A status code and {"detail": …} — a sentence, or {code, message} for errors your code may branch on (HTTP API). |
App users
Owners and admins of the organization manage the project's app users. user_id is the user's id (node_id in the listing).
GET/api/v1/projects/{project_id}/auth/users
List the project's app users, newest first.
Pages with a cursor: when more remain, the response has an X-Next-Cursor header — pass it back as cursor. The first page (no cursor) also has X-Total-Count for the same filters.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action.
Path parameters
project_idstringrequired- The project.
Query parameters
limitintegeroptionaldefault500- Users per page (1–500).
- 1–500
cursorstring | nulloptional- From the previous page's
X-Next-Cursor. - 0–200 characters
qstring | nulloptional- Part of an email, or an exact user id.
- 0–200 characters
statusstring | nulloptionalactiveorbanned.- pattern `^(active|banned)
HTTP API: users and auth settings | Sovaria Docs node_idstring- The user's id — the one every other call takes as
user_id, andauth.uid()in SQL. idstring- The id of their sign-in record (rarely needed).
emailstringis_activebooleanfalsewhile banned.created_at, updated_at, last_sign_in_atstring | null- ISO 8601.
mfa_enabled, phone_mfa_enabled, any_mfa_enabledboolean- Authenticator app, SMS, either.
rolesarray<string>- Their app roles in this project (Users and app roles).
role_sourcesarray<string>- Where the roles come from.
project_idstring- 200Success.
- 403Only organization owners and admins can list app users.
- 401No valid session or key.
- Response headers:
X-Next-Cursor: eyJ0cyI6…when there is a next page;X-Total-Count: 1284on the first page. - Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. project_idstringrequired- The project.
namestringrequired- Display name.
- 1–255 characters
emailstringrequired- Sign-in email.
passwordstring | nulloptional- Optional; at least 8 characters.
- at least 8 characters
rolesarray<string>optional- App roles to give them.
send_magic_linkbooleanoptionaldefaultfalse- Email a sign-in link now.
email_confirmbooleanoptionaldefaultfalse- Treat the email as confirmed.
env_idstring | nulloptional- An environment of the project.
node_idstring- The user's id — the one every other call takes as
user_id, andauth.uid()in SQL. idstring- The id of their sign-in record (rarely needed).
emailstringis_activebooleanfalsewhile banned.created_at, updated_at, last_sign_in_atstring | null- ISO 8601.
mfa_enabled, phone_mfa_enabled, any_mfa_enabledboolean- Authenticator app, SMS, either.
rolesarray<string>- Their app roles in this project (Users and app roles).
role_sourcesarray<string>- Where the roles come from.
project_idstringpending_inviteboolean- A sign-in link was sent and the email isn't confirmed yet.
- 201Created.
- 409That email is already registered in this project.
- 422
UNKNOWN_APP_ROLE— a role isn't defined; the body listsroles. - 403Only organization owners and admins.
- 429Too many sign-in links for this address or IP.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. project_idstringrequired- The project.
usersarray<object>required- 1–1000 items
9 fields
emailstringrequiredidstringoptional- Their id in the old system.
email_verifiedbooleanoptionalphonestringoptionaldisplay_namestringoptionalpassword_algorithmstringoptional- One of
bcrypt,firebase_scrypt password_hashstringoptionalpassword_saltstringoptional- Firebase only.
sourcestringoptional- Where they came from, kept in the user's metadata.
hash_configobjectoptional- Your Firebase project's password hash parameters (Firebase console › Authentication › Password hash parameters).
4 fields
signer_keystringrequired- Base64.
salt_separatorstringrequired- Base64.
roundsintegerrequiredmem_costintegerrequired
importedarray<object>user(email),id(their id here),source_id,password(bcrypt,firebase_scryptornone).skipped, failedarray<object>userandreason.countsobject- 200Success.
- 413More than 1,000 users in one call.
- 422
hash_configis invalid. - 403Only organization owners and admins.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. project_idstringrequired- The project.
user_idstringrequired- The user's id (
node_id). - 204Success, with no body.
- 404No such user in this project.
- 403Only organization owners and admins.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. project_idstringrequired- The project.
user_idstringrequired- The user.
- 200Success.
- 400You can't ban yourself.
- 404No such user.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. project_idstringrequired- The project.
user_idstringrequired- The user.
- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. project_idstringrequired- The project.
user_idstringrequired- The user.
rolesarray<string>optional- The full list;
[]removes all. - 200Success.
- 422
UNKNOWN_APP_ROLE— a role isn't defined in the project. - 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. project_idstringrequired- The project.
user_idstringrequired- The user.
mfa_enabledboolean- Authenticator app.
phone_mfa_enabledboolean- SMS codes.
totp_enrolledboolean- An authenticator secret is stored.
any_mfa_enabledboolean- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. project_idstringrequired- The project.
user_idstringrequired- The user.
- 200Success.
- 404No such user in this project.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/product-users/{user_id}/test-sessionuser_idstringrequired- The user.
minutesintegeroptionaldefault15- How long it lasts (1–60).
- 1.0–60.0
- 200Success.
- 400No organization/project scope.
- 403Not an owner/admin, or called with a test session.
- 404No such user.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- The user themselves, or an organization admin.
- Also at
/auth/v1/users/{user_id}/timelineuser_idstringrequired- The user.
atstring | nulloptional- Only events at or before this time (ISO 8601).
event_typestring | nulloptional- One event type.
limitintegeroptionaldefault50- How many (at most 200).
offsetintegeroptionaldefault0- How many to skip.
- 200Success.
- 400
atisn't a valid time. - 403Someone else's timeline without admin access.
- 401No valid session or key.
- Who
- The user themselves, or an organization admin.
- Also at
/auth/v1/users/{user_id}/timeline/stateuser_idstringrequired- The user.
atstringrequired- The moment (ISO 8601); now by default.
- 200Success.
- 401No valid session or key.
- Who
- The user themselves, or an organization owner/admin for users in their organization.
- Also at
/auth/v1/users/{user_id}/data-exportuser_idstringrequired- The user.
user_idstringrequired- The app user's id.
exported_atstringrequiredprofileobjectrequiredauth_methodsarray<object>requiredapi_keysarray<object>requiredwebhooksarray<object>requiredauth_eventsarray<object>required- 200Success.
- 400
user_idisn't a UUID. - 403Not that user or an admin of their organization.
- 404No such user.
- 401No valid session or key.
- Who
- The user themselves, or an organization owner/admin for users in their organization.
- Also at
/auth/v1/users/{user_id}/delete-requestuser_idstringrequired- The user.
user_idstringrequired- The app user's id.
statusstringrequiredscheduled_atstringrequiredhard_delete_atstringrequiredmessagestringrequired- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/providers/configprovidersarray<object>id,label,enabled,requested_enabled,available,managed_by,description,setup_required, andunavailable_reasonwhen it can't be used.sms_configobjectprovider(log,twilio,custom),account_sid,from_number, and whether an auth token is stored (auth_token_set,auth_token_hint). The token itself is never returned.database_linkobject- Kept for older clients; not used by projects.
- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/providers/configprovidersarray<object>optional2 fields
idstringrequired- Provider id.
enabledbooleanrequired
sms_configobjectoptional4 fields
providerstringoptionallogwrites codes to the logs (development).- One of
log,twilio,custom account_sidstringoptionalfrom_numberstringoptionalauth_tokenstringoptional- Stored encrypted; omit to keep the current one.
database_linkobjectoptional- Ignored for projects.
- 200The same shape as GET, after the change.
- 403
PLAN_LIMIT_REACHED— more social providers than the plan allows. - 401No valid session or key.
- Who
- Any signed-in account (it applies to that account).
- Also at
/auth/v1/sessions/configtimeout_minutesinteger- Access token lifetime.
refresh_daysinteger- How long a session can be refreshed.
max_concurrentinteger- Open sessions allowed (0 = no limit).
single_sessionboolean- Signing in ends the other sessions.
- 200Success.
- 401No valid session or key.
- Who
- Any signed-in account (it applies to that account).
- Also at
/auth/v1/sessions/configtimeout_minutesintegerrequired- Access token lifetime, 1–10080.
- 1.0–10080.0
refresh_daysintegerrequired- 1–365.
- 1.0–365.0
max_concurrentintegerrequired- 0–100 (0 = no limit).
- 0.0–100.0
single_sessionbooleanoptionaldefaultfalse- Keep only this session.
- 200Success.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/smtp-configenabledboolean- Custom SMTP is on and has a host — emails go through it.
customSmtpEnabledboolean- The switch itself.
host, port, user, from_addressstring | integerpassword_set, password_hintboolean, string- Whether a password is stored, and its first and last two characters.
- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/smtp-configcustomSmtpEnabledboolean | nulloptional- Use your server.
hoststring | nulloptional- SMTP host.
portinteger | nulloptional- Usually 587 (STARTTLS) or 465.
- 1.0–65535.0
userstring | nulloptional- SMTP user.
passwordstring | nulloptional- SMTP password (stored encrypted).
from_addressstring | nulloptional- Sender, e.g.
Acme <no-reply@acme.com>. - 200The same shape as GET.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/email-templates<template key>objectsubject,body_html,body_text.- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/email-templates/{template_key}template_keystringrequired- Which email (see the list above).
subjectstring | nulloptional- Subject line.
body_htmlstring | nulloptional- HTML body.
body_textstring | nulloptional- Plain-text body.
- 200Success.
- 400Unknown template key (the message lists the valid ones).
- 403
PLAN_CUSTOM_EMAIL_TEMPLATES_REQUIRED— not in the plan. - 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/email-templates/{template_key}template_keystringrequired- Which email.
- 200Success.
- 400Unknown template key.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/event-hooksdelivery_count, failure_countintegerlast_errorstring | null- A short code such as
http_503ornetwork— never the response body. - 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/event-hooksnamestringrequired- A label.
- 1–128 characters
urlstringrequired- HTTPS endpoint (private addresses are refused).
eventsarray<string>required- Event types, or
["*"]. - 1–… items
secretstringoptional- Signing secret (stored encrypted).
- 0–256 characters
enabledbooleanoptionaldefaulttrue- On or off.
project_idstring | nulloptional- The project, if not sent as a header.
- 201Created.
- 400Unknown event type, a private or non-HTTPS URL, no project, or already 20 hooks.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/event-hooks/{hook_id}hook_idstringrequired- The hook.
- 204Success, with no body.
- 404No such hook.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/jwt-enrichment-hook- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/jwt-enrichment-hookurlstringrequired- HTTPS URL.
secretstringoptional- HMAC-SHA256 signing secret.
enabledbooleanoptionaldefaulttrue- On or off.
- 201Created.
- 400Private or invalid URL.
- 403
PLAN_FEATURE_REQUIRED— custom claims aren't in the plan. - 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/jwt-enrichment-hook- 204Success, with no body.
- 404None is set.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/oidc/presetskindstringoidcoroauth2.console_urlstring- Where to create the OAuth app.
callback_urlstring- The redirect URI to register there.
- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/oidc/providers- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/oidc/providerspresetstringoptionaldefaultcustom_oidc- A preset id, or
custom_oidc. namestring | nulloptional- Shown on the sign-in button.
- 0–128 characters
discovery_urlstring | nulloptionalhttps://…/.well-known/openid-configuration(required forcustom_oidc).- 0–2,048 characters
client_idstringrequired- From the provider.
- 1–512 characters
client_secretstringrequired- From the provider (stored encrypted).
- 1–8,192 characters
allowed_domainsarray<string>optional- Only these email domains may sign in (empty = any).
scopesarray<string> | nulloptional- Scopes to ask for (the preset's by default).
enabledbooleanoptionaldefaulttrue- On or off.
- 201The provider, as in the list (with
has_secret: trueand itscallback_url). - 400No https discovery URL, or discovery failed (the message says why).
- 403
PLAN_LIMIT_REACHED— more providers than the plan allows. - 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/oidc/providers/{provider_id}provider_idstringrequired- The provider.
namestring | nulloptional- Name.
- 0–128 characters
client_idstring | nulloptional- Client id.
- 1–512 characters
client_secretstring | nulloptional- New secret.
- 1–8,192 characters
allowed_domainsarray<string> | nulloptional- Domains.
scopesarray<string> | nulloptional- Scopes.
enabledboolean | nulloptional- On or off.
- 200The provider.
- 404No such provider.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/oidc/providers/{provider_id}provider_idstringrequired- The provider.
- 204Success, with no body.
- 404No such provider.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/oauth-apps- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/oauth-serverenabledbooleanrequired- On or off.
- 200Success.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/oauth-appsnamestringrequired- The app's name.
- 1–128 characters
redirect_urisarray<string>required- Where users return after signing in.
- 1–… items
scopesarray<string> | nulloptional- Scopes it may ask for.
enabledbooleanoptionaldefaulttrue- On or off.
- 201Created.
- 400Invalid redirect URI, no name, or already 50 apps.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/oauth-apps/{app_id}app_idstringrequired- The app's
id. namestring | nulloptional- Name.
- 1–128 characters
redirect_urisarray<string> | nulloptional- Redirect URIs.
- 1–… items
scopesarray<string> | nulloptional- Scopes.
enabledboolean | nulloptional- On or off.
- 200The app (without the secret).
- 400Nothing to change, or an invalid value.
- 404No such app.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/oauth-apps/{app_id}app_idstringrequired- The app.
- 204Success, with no body.
- 404No such app.
- 401No valid session or key.
- Who
- Anyone: no key or session is needed.
- Also at
/auth/v1/.well-known/jwks.json,/auth/v1/project-jwks- 200Success.
- 401No valid session or key.
- Who
- Anyone: no key or session is needed.
- Also at
/auth/v1/saml/loginorg_idstringrequired- The organization.
- 200302 redirect to the identity provider.
- 400SAML isn't enabled or has no IdP SSO URL.
- 403SAML SSO requires the Team plan or higher.
- 401No valid session or key.
- Who
- Anyone: no key or session is needed.
- Also at
/auth/v1/saml/metadata/{org_id}org_idstringrequired- The organization.
- 200
application/samlmetadata+xmlwith the entity id and the ACS URL…/api/v1/auth/saml/acs. - 401No valid session or key.
- Who
- Anyone: no key or session is needed.
- Also at
/auth/v1/github/loginredirect_uristringrequired- Where to return (a Sovaria address).
project_idstring | nulloptional- Connect GitHub to this project instead of signing in.
accessstring | nulloptionalrepofor repository write access.- 200302 redirect to GitHub.
- 400
redirect_uriisn't allowed. - Who
- Anyone: no key or session is needed.
- Also at
/auth/v1/google/loginredirect_uristringrequired- Where to return (a Sovaria address).
- 200302 redirect to Google.
- 400
redirect_uriisn't allowed. - 401No valid session or key.
- Who
- Anyone: no key or session is needed.
- Also at
/auth/v1/microsoft/loginredirect_uristringrequired- Where to return (a Sovaria address).
- 200302 redirect to Microsoft.
- 400
redirect_uriisn't allowed. - 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/github/connection-stateproject_idstring | nulloptional- A project; your account by default.
- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/github/connection-stateproject_idstring | nulloptional- A project; your account by default.
- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/cli/oauth/authorizeclient_idstringrequired- The CLI's client id.
- at least 1 characters
redirect_uristringrequired- The CLI's loopback URL.
- at least 1 characters
code_challengestringrequired- PKCE challenge.
- at least 1 characters
code_challenge_methodstringoptionaldefaultS256S256.statestringoptional- The CLI's state value.
redirect_urlstringrequired- 200Success.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/product-userslimitintegeroptionaldefault500- Users per page (1–500).
- 1–500
cursorstring | nulloptional- From the previous page's
X-Next-Cursor. - 0–200 characters
qstring | nulloptional- Part of an email, or an exact user id.
- 0–200 characters
statusstring | nulloptionalactiveorbanned.- pattern `^(active|banned)
HTTP API: users and auth settings | Sovaria Docs node_idstring- The user's id — the one every other call takes as
user_id, andauth.uid()in SQL. idstring- The id of their sign-in record (rarely needed).
emailstringis_activebooleanfalsewhile banned.created_at, updated_at, last_sign_in_atstring | null- ISO 8601.
mfa_enabled, phone_mfa_enabled, any_mfa_enabledboolean- Authenticator app, SMS, either.
rolesarray<string>- Their app roles in this project (Users and app roles).
role_sourcesarray<string>- Where the roles come from.
project_idstring- 200Success.
- 403Only organization owners and admins can list app users.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/product-usersnamestringrequired- Display name.
- 1–255 characters
emailstringrequired- Sign-in email.
passwordstring | nulloptional- Optional; at least 8 characters.
- at least 8 characters
rolesarray<string>optional- App roles to give them.
send_magic_linkbooleanoptionaldefaultfalse- Email a sign-in link now.
email_confirmbooleanoptionaldefaultfalse- Treat the email as confirmed.
env_idstring | nulloptional- An environment of the project.
node_idstring- The user's id — the one every other call takes as
user_id, andauth.uid()in SQL. idstring- The id of their sign-in record (rarely needed).
emailstringis_activebooleanfalsewhile banned.created_at, updated_at, last_sign_in_atstring | null- ISO 8601.
mfa_enabled, phone_mfa_enabled, any_mfa_enabledboolean- Authenticator app, SMS, either.
rolesarray<string>- Their app roles in this project (Users and app roles).
role_sourcesarray<string>- Where the roles come from.
project_idstringpending_inviteboolean- A sign-in link was sent and the email isn't confirmed yet.
- 201Created.
- 409That email is already registered in this project.
- 422
UNKNOWN_APP_ROLE— a role isn't defined; the body listsroles. - 403Only organization owners and admins.
- 429Too many sign-in links for this address or IP.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/product-users/{user_id}user_idstringrequired- The user's id (
node_id). - 204Success, with no body.
- 404No such user in this project.
- 403Only organization owners and admins.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/product-users/{user_id}/banuser_idstringrequired- The user.
- 200Success.
- 400You can't ban yourself.
- 404No such user.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/product-users/{user_id}/rolesuser_idstringrequired- The user.
- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/product-users/{user_id}/rolesuser_idstringrequired- The user.
rolesarray<string>optional- The full list;
[]removes all. - 200Success.
- 422
UNKNOWN_APP_ROLE— a role isn't defined in the project. - 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/product-users/{user_id}/mfauser_idstringrequired- The user.
mfa_enabledboolean- Authenticator app.
phone_mfa_enabledboolean- SMS codes.
totp_enrolledboolean- An authenticator secret is stored.
any_mfa_enabledboolean- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/product-users/{user_id}/mfa/resetuser_idstringrequired- The user.
- 200Success.
- 404No such user in this project.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/userslimitintegeroptionaldefault500- Users per page (1–500).
- 1–500
cursorstring | nulloptional- From the previous page's
X-Next-Cursor. - 0–200 characters
qstring | nulloptional- Part of an email, or an exact user id.
- 0–200 characters
statusstring | nulloptionalactiveorbanned.- pattern `^(active|banned)
HTTP API: users and auth settings | Sovaria Docs node_idstring- The user's id — the one every other call takes as
user_id, andauth.uid()in SQL. idstring- The id of their sign-in record (rarely needed).
emailstringis_activebooleanfalsewhile banned.created_at, updated_at, last_sign_in_atstring | null- ISO 8601.
mfa_enabled, phone_mfa_enabled, any_mfa_enabledboolean- Authenticator app, SMS, either.
rolesarray<string>- Their app roles in this project (Users and app roles).
role_sourcesarray<string>- Where the roles come from.
project_idstring- 200Success.
- 403Only organization owners and admins can list app users.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/users/{user_id}user_idstringrequired- The user's id (
node_id). - 204Success, with no body.
- 404No such user in this project.
- 403Only organization owners and admins.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/users/{user_id}/banuser_idstringrequired- The user.
- 200Success.
- 400You can't ban yourself.
- 404No such user.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/users/{user_id}/rolesuser_idstringrequired- The user.
- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/users/{user_id}/rolesuser_idstringrequired- The user.
rolesarray<string>optional- The full list;
[]removes all. - 200Success.
- 422
UNKNOWN_APP_ROLE— a role isn't defined in the project. - 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/users/{user_id}/mfauser_idstringrequired- The user.
mfa_enabledboolean- Authenticator app.
phone_mfa_enabledboolean- SMS codes.
totp_enrolledboolean- An authenticator secret is stored.
any_mfa_enabledboolean- 200Success.
- 401No valid session or key.
- Who
- A project team member's session (the console, or a token from
sov login) asAuthorization: Bearer <token>. The member's role must allow the action. - Also at
/auth/v1/users/{user_id}/mfa/resetuser_idstringrequired- The user.
- 200Success.
- 404No such user in this project.
- 401No valid session or key.
Returns
Responses
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/auth/users?limit=50&status=active" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
[
{
"id": "a71d0c5e-2f84-4b9a-9e13-6c5d8f2b1a47",
"node_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"email": "ana@example.com",
"is_active": true,
"created_at": "2026-10-02T08:14:22.481Z",
"updated_at": "2026-10-16T19:03:51.112Z",
"mfa_enabled": false,
"phone_mfa_enabled": false,
"any_mfa_enabled": false,
"last_sign_in_at": "2026-10-17T09:41:05.330Z",
"roles": [
"staff"
],
"role_sources": [
"project_settings"
],
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07"
}
]POST/api/v1/projects/{project_id}/auth/users
Create an app user (as an admin).
Without a password, the user signs in by magic link or invite: send_magic_link: true emails one. email_confirm: true marks the email confirmed so no verification email is needed. Roles must exist in the project's role list.
Path parameters
Body
Returns
Responses
curl -s -X POST "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/auth/users" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "Ana Lima", "email": "ana@example.com", "roles": ["staff"], "send_magic_link": true}'Response
{
"id": "a71d0c5e-2f84-4b9a-9e13-6c5d8f2b1a47",
"node_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"email": "ana@example.com",
"is_active": true,
"created_at": "2026-10-02T08:14:22.481Z",
"updated_at": "2026-10-16T19:03:51.112Z",
"mfa_enabled": false,
"phone_mfa_enabled": false,
"any_mfa_enabled": false,
"last_sign_in_at": null,
"roles": [
"staff"
],
"role_sources": [
"project_settings"
],
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"actor_lane": "product",
"auth_subject_type": "product_user",
"pending_invite": true
}POST/api/v1/projects/{project_id}/auth/users/import
Import up to 1,000 users at once, keeping their ids and password hashes (from Supabase, Firebase or your own system).
Nothing is emailed and no sign-up hooks or function triggers run. Emails already in the project are skipped. Passwords: bcrypt hashes (Supabase) are kept as they are; firebase_scrypt hashes are checked with your Firebase project's hash parameters (hash_config, stored encrypted) and upgraded to bcrypt at the user's first sign-in. Users without a password sign in by magic link or a provider. A UUID id is kept (so rows referencing the user still match); other ids (Firebase uids) get a new id and the old one is returned as source_id. The CLI does this for you: sov import supabase / sov import firebase (Migrate from Supabase or Firebase).
Path parameters
Body
Returns
Responses
curl -s -X POST "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/auth/users/import" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"users": [{"id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93", "email": "ana@example.com", "email_verified": true, "password_algorithm": "bcrypt", "password_hash": "$2a$10$Q3m…"}, {"id": "kX9fF2…", "email": "li@example.com", "password_algorithm": "firebase_scrypt", "password_hash": "lSrfV1…", "password_salt": "42xEC…"}], "hash_config": {"signer_key": "jxspr8Ki0…", "salt_separator": "Bw==", "rounds": 8, "mem_cost": 14}}'Response
{
"imported": [
{
"user": "ana@example.com",
"id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"source_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"password": "bcrypt"
},
{
"user": "li@example.com",
"id": "9e2b…",
"source_id": "kX9fF2…",
"password": "firebase_scrypt"
}
],
"skipped": [
{
"user": "old@example.com",
"reason": "already in this project"
}
],
"failed": [
{
"user": "bad",
"reason": "missing or invalid email"
}
],
"counts": {
"imported": 2,
"skipped": 1,
"failed": 1
}
}DELETE/api/v1/projects/{project_id}/auth/users/{user_id}
Delete an app user permanently and sign them out everywhere.
Their sign-in record is removed for good and every session ends. To keep the account but stop sign-ins, ban instead.
Path parameters
Responses
curl -s -X DELETE "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"POST/api/v1/projects/{project_id}/auth/users/{user_id}/ban
Ban an app user, or unban them — each call switches it.
A banned user can't sign in or refresh a session. is_active in the answer is the new state.
Path parameters
Responses
curl -s -X POST "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/ban" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"is_active": false
}GET/api/v1/projects/{project_id}/auth/users/{user_id}/roles
A user's app roles.
Path parameters
Responses
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/roles" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"org_id": "b2f4a1c8-7d3e-4f0a-9c6b-5e8d2a1f3c90",
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"roles": [
"staff"
]
}PUT/api/v1/projects/{project_id}/auth/users/{user_id}/roles
Replace a user's app roles.
Takes effect at their next token (refresh, or sign-in). Roles are what ctx.actor.roles in rules and auth.jwt() in SQL see (Users and app roles).
Path parameters
Body
Responses
curl -s -X PUT "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/roles" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"roles": ["staff", "billing"]}'Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"org_id": "b2f4a1c8-7d3e-4f0a-9c6b-5e8d2a1f3c90",
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"roles": [
"billing",
"staff"
]
}GET/api/v1/projects/{project_id}/auth/users/{user_id}/mfa
Whether a user has two-factor sign-in turned on.
Path parameters
Returns
Responses
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/mfa" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"mfa_enabled": true,
"phone_mfa_enabled": false,
"totp_enrolled": true,
"any_mfa_enabled": true
}POST/api/v1/projects/{project_id}/auth/users/{user_id}/mfa/reset
Turn off a user's two-factor sign-in (they lost their phone).
Removes their authenticator and SMS factors; they sign in with their password alone and can enrol again. Recorded in the user's security activity.
Path parameters
Responses
curl -s -X POST "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/mfa/reset" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"status": "reset",
"message": "MFA has been reset for this user",
"mfa_enabled": false,
"phone_mfa_enabled": false,
"totp_enrolled": false,
"any_mfa_enabled": false
}POST/api/v1/auth/product-users/{user_id}/test-session
Get a short-lived access token for one of your app users, to test your API and rules as them.
For owners and admins. The token is a real access token for that user (no refresh token), lasting minutes (default 15, at most 60), marked test_session — it can't create further test sessions. Every issue is audited.
Path parameters
Body
Responses
curl -s -X POST "$URL/api/v1/auth/product-users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/test-session" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"minutes": 15}'Response
{
"access_token": "eyJhbGciOiJFUzI1NiIs…",
"token_type": "bearer",
"expires_in": 900,
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"email": "ana@example.com",
"roles": [
"staff"
],
"test_session": true
}Activity and data requests
GET/api/v1/auth/users/{user_id}/timeline
A user's sign-in and security events, newest first.
Event types include auth.login.success, auth.login.failed, auth.logout, auth.token.refresh, auth.password.changed, auth.password.reset, auth.mfa.enrolled, auth.mfa.verified, auth.mfa.disabled, auth.passkey.registered, auth.session.revoked, auth.email.changed.
Path parameters
Query parameters
Responses
curl -s "$URL/api/v1/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/timeline?limit=20" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"at": null,
"events": [
{
"id": "5d2f…",
"event_type": "auth.login.success",
"meta": {
"method": "password",
"aal": "aal1",
"ip": "203.0.113.24",
"device": "Chrome on macOS"
},
"created_at": "2026-10-17T09:41:05.330Z"
}
],
"count": 1,
"limit": 20,
"offset": 0
}GET/api/v1/auth/users/{user_id}/timeline/state
What a user's account looked like at a moment in time (for investigations).
Rebuilt from the timeline: the last sign-in before at, whether two-factor and passkeys were set up, whether the account was banned, and which sessions were open (approximate — expired sessions aren't recorded).
Path parameters
Query parameters
Responses
curl -s "$URL/api/v1/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/timeline/state?at=2026-10-10T00:00:00Z" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"at": "2026-10-10T00:00:00+00:00",
"last_login": "2026-10-09T18:22:10.004Z",
"last_method": "password",
"last_aal": "aal2",
"was_mfa_enrolled": true,
"had_passkey": false,
"is_active": true,
"active_sessions": [
"9f1c2e…"
],
"mfa_factors": [
{
"type": "totp",
"enrolled_at": "2026-10-03T11:00:12.551Z"
}
],
"passkeys_registered": [],
"total_events_at": 41
}POST/api/v1/auth/users/{user_id}/data-export
Everything Sovaria holds about a user, as one JSON document (data-protection requests).
Covers the account, sign-in records, API keys, webhooks they own and their security events. Data in your own tables isn't included — export that with SQL.
Path parameters
Returns
Responses
curl -s -X POST "$URL/api/v1/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/data-export" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"exported_at": "2026-10-18T10:02:11.900Z",
"profile": {
"email": "ana@example.com",
"is_active": true,
"email_verified": true,
"mfa_enabled": false,
"created_at": "2026-10-02T08:14:22.481Z",
"updated_at": "2026-10-16T19:03:51.112Z"
},
"auth_methods": [
{
"email": "ana@example.com",
"org_id": "b2f4a1c8-7d3e-4f0a-9c6b-5e8d2a1f3c90",
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"env_id": null,
"mfa_enabled": false,
"created_at": "2026-10-02T08:14:22.481Z"
}
],
"api_keys": [],
"webhooks": [],
"auth_events": [
{
"event_type": "auth.login.success",
"meta": {
"method": "password"
},
"created_at": "2026-10-17T09:41:05.330Z"
}
]
}POST/api/v1/auth/users/{user_id}/delete-request
Start deleting a user's account: deactivated now, erased after 30 days.
The account is deactivated and every session revoked at once; the permanent deletion is scheduled 30 days later. Calling again returns the existing schedule.
Path parameters
Returns
Responses
curl -s -X POST "$URL/api/v1/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/delete-request" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"status": "scheduled",
"scheduled_at": "2026-10-18T10:05:00.000Z",
"hard_delete_at": "2026-11-17T10:05:00.000Z",
"message": "Account deactivated. Permanent deletion scheduled in 30 days."
}Sign-in methods, sessions and email
GET/api/v1/auth/providers/config
Which sign-in methods are on for the project, and the SMS settings.
enabled is true only when the method is turned on and available (requested_enabled is what you asked for). Provider ids: email, magic_link, phone_otp, github, google, gitlab, microsoft, apple, discord, linkedin, facebook, x_twitter, saml, oidc, custom_jwt. Social sign-in with your own OAuth app is set up under OIDC providers below.
Returns
Responses
curl -s "$URL/api/v1/auth/providers/config" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"providers": [
{
"id": "email",
"label": "Email + Password",
"enabled": true,
"requested_enabled": true,
"available": true,
"managed_by": "core",
"description": "Built-in credential authentication.",
"setup_required": []
},
{
"id": "magic_link",
"label": "Magic Link",
"enabled": true,
"requested_enabled": true,
"available": true,
"managed_by": "core",
"description": "…",
"setup_required": []
}
],
"database_link": {
"enabled": false,
"engine": "postgresql",
"host": "",
"port": 5432,
"database": "",
"schema": "public",
"username": "",
"ssl": true
},
"sms_config": {
"provider": "twilio",
"account_sid": "AC5f…",
"from_number": "+14155550123",
"auth_token_set": true,
"auth_token_hint": "8c…f2"
}
}PUT/api/v1/auth/providers/config
Turn sign-in methods on or off, and set the SMS provider for phone codes.
Send only what changes. Turning on more social providers than the plan allows is refused (auth.oauth_providers).
Body
Responses
curl -s -X PUT "$URL/api/v1/auth/providers/config" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"providers": [{"id": "magic_link", "enabled": true}, {"id": "phone_otp", "enabled": true}], "sms_config": {"provider": "twilio", "account_sid": "AC5f…", "from_number": "+14155550123", "auth_token": "<twilio auth token>"}}'GET/api/v1/auth/sessions/config
Your own session policy: how long sessions last and how many you may have.
Returns
Responses
curl -s "$URL/api/v1/auth/sessions/config" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"timeout_minutes": 15,
"refresh_days": 7,
"max_concurrent": 0,
"single_session": false
}PUT/api/v1/auth/sessions/config
Change your own session policy.
Body
Responses
curl -s -X PUT "$URL/api/v1/auth/sessions/config" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"timeout_minutes": 30, "refresh_days": 14, "max_concurrent": 5, "single_session": false}'Response
{
"timeout_minutes": 30,
"refresh_days": 14,
"max_concurrent": 5,
"single_session": false
}GET/api/v1/auth/smtp-config
The project's own SMTP server for auth emails, if set (the password is never returned).
Returns
Responses
curl -s "$URL/api/v1/auth/smtp-config" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"enabled": true,
"host": "smtp.postmarkapp.com",
"port": 587,
"user": "pm-user",
"from_address": "Acme <no-reply@acme.com>",
"password_set": true,
"password_hint": "Xy…9q",
"customSmtpEnabled": true
}PUT/api/v1/auth/smtp-config
Send the project's auth emails through your own SMTP server.
Send only what changes; omit password to keep the stored one. Until it's on, Sovaria's mail service sends them (Emails: SMTP, templates and limits).
Body
Responses
curl -s -X PUT "$URL/api/v1/auth/smtp-config" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"customSmtpEnabled": true, "host": "smtp.postmarkapp.com", "port": 587, "user": "pm-user", "password": "<smtp password>", "from_address": "Acme <no-reply@acme.com>"}'GET/api/v1/auth/email-templates
The project's auth email templates (yours where changed, the defaults otherwise).
Keys: email_verification, password_reset, magic_link, password_changed, new_sign_in, mfa_challenge, email_change_confirm_old, email_change_confirm_new, invite_user. {{ placeholders }} are filled when sending: project_name everywhere, plus per template confirmation_url, reset_url, magic_link_url, otp_code, code, ttl_minutes, ttl_hours, new_email, confirm_url, finalize_url, invite_url, ip, device, signed_in_at, changed_at, email.
Returns
Responses
curl -s "$URL/api/v1/auth/email-templates" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"magic_link": {
"subject": "Your sign-in link for {{ project_name }}",
"body_html": "<!doctype html>…{{ magic_link_url }}…",
"body_text": "Sign in to {{ project_name }}: {{ magic_link_url }} …"
},
"password_reset": {
"subject": "Reset your {{ project_name }} password",
"body_html": "…",
"body_text": "…"
}
}PUT/api/v1/auth/email-templates/{template_key}
Change one auth email: subject, HTML and plain-text body.
Send only the parts you change. Custom templates are a paid feature (custom_email_templates).
Path parameters
Body
Responses
curl -s -X PUT "$URL/api/v1/auth/email-templates/magic_link" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"subject": "Your Acme sign-in link", "body_text": "Hi! Sign in to Acme: {{ magic_link_url }}\nThe link works for {{ ttl_minutes }} minutes."}'Response
{
"template_key": "magic_link",
"subject": "Your Acme sign-in link",
"body_html": "…",
"body_text": "Hi! Sign in to Acme: {{ magic_link_url }}\nThe link works for {{ ttl_minutes }} minutes."
}DELETE/api/v1/auth/email-templates/{template_key}
Go back to the default for one email (allowed on every plan).
Path parameters
Responses
curl -s -X DELETE "$URL/api/v1/auth/email-templates/magic_link" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"template_key": "magic_link",
"status": "reset_to_default",
"subject": "Your sign-in link for {{ project_name }}",
"body_html": "…",
"body_text": "…"
}Auth hooks and custom claims
GET/api/v1/auth/event-hooks
Webhooks that receive the project's auth events.
Returns
Responses
curl -s "$URL/api/v1/auth/event-hooks" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
[
{
"id": "0b6e…",
"name": "CRM sync",
"url": "https://hooks.acme.com/sovaria-auth",
"events": [
"auth.user.login.success",
"auth.user.registered"
],
"enabled": true,
"created_at": "2026-10-01T10:00:00+00:00",
"updated_at": "2026-10-01T10:00:00+00:00",
"delivery_count": 412,
"failure_count": 3,
"last_delivery_at": "2026-10-17T09:41:05+00:00",
"last_error": null
}
]POST/api/v1/auth/event-hooks
Send auth events (sign-ups, sign-ins, bans…) to your URL.
Events: auth.user.registered, auth.user.login.success, auth.user.login.failed, auth.user.logout, auth.user.password_reset, auth.user.email_verified, auth.user.banned, or * for all. Up to 20 hooks per project. Each delivery is a POST of {id, type, project_id, timestamp, data}; with a secret it carries X-Sovaria-Signature: sha256=<hex HMAC of the body>. Deliveries time out after 10 s and are retried after 0.5 s, 1 s and 2 s; the sign-in itself never waits for them (Auth event hooks).
Body
Responses
curl -s -X POST "$URL/api/v1/auth/event-hooks" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "CRM sync", "url": "https://hooks.acme.com/sovaria-auth", "events": ["auth.user.registered", "auth.user.login.success"], "secret": "<random secret>"}'Response
{
"id": "0b6e…",
"name": "CRM sync",
"url": "https://hooks.acme.com/sovaria-auth",
"events": [
"auth.user.login.success",
"auth.user.registered"
],
"enabled": true,
"created_at": "2026-10-18T10:10:00+00:00",
"updated_at": "2026-10-18T10:10:00+00:00",
"delivery_count": 0,
"failure_count": 0,
"last_delivery_at": null,
"last_error": null
}DELETE/api/v1/auth/event-hooks/{hook_id}
Remove an auth event hook.
Path parameters
Responses
curl -s -X DELETE "$URL/api/v1/auth/event-hooks/0b6e…" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"GET/api/v1/auth/jwt-enrichment-hook
The custom-claims hook, or null when none is set.
Responses
curl -s "$URL/api/v1/auth/jwt-enrichment-hook" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"url": "https://api.acme.com/claims",
"enabled": true
}POST/api/v1/auth/jwt-enrichment-hook
Add your own claims to every access token: Sovaria asks your URL each time it issues one.
Your URL receives POST {user_id, project_id, claims} (signed with the secret) and answers {"claims": {…}}; those keys are added to the token's ctx. It must answer within 50 ms — otherwise the token is issued without them. Keys Sovaria owns (roles, org_id, project_id, env_id, aal, actor_lane, lane, plan, permissions…) are dropped. Needs the auth.custom_jwt_claims plan feature (Authentication settings reference).
Body
Responses
curl -s -X POST "$URL/api/v1/auth/jwt-enrichment-hook" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"url": "https://api.acme.com/claims", "secret": "<random secret>"}'Response
{
"url": "https://api.acme.com/claims",
"enabled": true
}DELETE/api/v1/auth/jwt-enrichment-hook
Remove the custom-claims hook.
Responses
curl -s -X DELETE "$URL/api/v1/auth/jwt-enrichment-hook" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Social and OIDC sign-in with your own OAuth app
GET/api/v1/auth/oidc/presets
Sign-in providers you can connect with your own OAuth app, and the callback URL to register with each.
Presets: google, microsoft, apple, gitlab, linkedin, slack, twitch, github, discord, facebook, spotify, bitbucket, sovaria, and custom_oidc for any OpenID Connect provider.
Returns
Responses
curl -s "$URL/api/v1/auth/oidc/presets" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
[
{
"id": "google",
"name": "Google",
"kind": "oidc",
"scopes": [
"openid",
"email",
"profile"
],
"console_url": "https://console.cloud.google.com/apis/credentials",
"discovery_url": "https://accounts.google.com/.well-known/openid-configuration",
"authorize_url": "",
"token_url": "",
"userinfo_url": "",
"emails_url": "",
"token_auth": "body",
"mapping": {},
"trusts_email": true,
"note": "",
"callback_url": "https://api.sovaria.in/api/v1/auth/sso/google/callback"
}
]GET/api/v1/auth/oidc/providers
The project's connected sign-in providers (secrets are never returned).
Responses
curl -s "$URL/api/v1/auth/oidc/providers" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
[
{
"preset": "google",
"kind": "oidc",
"discovery_url": "https://accounts.google.com/.well-known/openid-configuration",
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"id": "google",
"name": "Google",
"client_id": "1234-abc.apps.googleusercontent.com",
"allowed_domains": [],
"scopes": [
"openid",
"email",
"profile"
],
"enabled": true,
"created_at": "2026-10-05T12:00:00Z",
"has_secret": true,
"callback_url": "https://api.sovaria.in/api/v1/auth/sso/google/callback"
}
]POST/api/v1/auth/oidc/providers
Connect a sign-in provider with your own OAuth app (Google, GitHub, Apple, any OIDC provider…).
Create the OAuth app at the provider first, with the preset's callback_url as its redirect URI. OIDC providers are checked now (their discovery document must load). A preset's id becomes the provider id (google); custom_oidc gets a generated id. The number of providers is limited by the plan (auth.oauth_providers). Apps then sign users in with GET /auth/sso/{id}/authorize (Social sign-in).
Body
Responses
curl -s -X POST "$URL/api/v1/auth/oidc/providers" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"preset": "google", "client_id": "1234-abc.apps.googleusercontent.com", "client_secret": "<client secret>", "allowed_domains": ["acme.com"]}'PATCH/api/v1/auth/oidc/providers/{provider_id}
Change a provider's name, credentials, allowed domains, scopes or switch it off.
Path parameters
Body
Responses
curl -s -X PATCH "$URL/api/v1/auth/oidc/providers/google" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"allowed_domains": ["acme.com", "acme.io"]}'DELETE/api/v1/auth/oidc/providers/{provider_id}
Disconnect a provider. Users who signed in with it keep their accounts.
Path parameters
Responses
curl -s -X DELETE "$URL/api/v1/auth/oidc/providers/google" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Your project as an OAuth provider
GET/api/v1/auth/oauth-apps
Apps that can sign in with your project as their OAuth provider ("Sign in with Acme").
Responses
curl -s "$URL/api/v1/auth/oauth-apps" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"server_enabled": true,
"apps": [
{
"id": "7f2c…",
"name": "Acme Analytics",
"client_id": "sov_3f9a1c0b7e2d4a6f8c1e",
"redirect_uris": [
"https://analytics.acme.com/callback"
],
"scopes": [
"email",
"openid",
"profile"
],
"enabled": true,
"created_at": "2026-10-11T09:00:00+00:00",
"client_secret_set": true
}
]
}PUT/api/v1/auth/oauth-server
Turn the project's OAuth server on or off.
Body
Responses
curl -s -X PUT "$URL/api/v1/auth/oauth-server" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"enabled": true}'Response
{
"enabled": true
}POST/api/v1/auth/oauth-apps
Register an app that signs in with your project. The client secret is shown only in this answer.
Redirect URIs must be https, or http on localhost. Scopes default to openid, profile, email. Up to 50 apps per project.
Body
Responses
curl -s -X POST "$URL/api/v1/auth/oauth-apps" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "Acme Analytics", "redirect_uris": ["https://analytics.acme.com/callback"]}'Response
{
"id": "7f2c…",
"name": "Acme Analytics",
"client_id": "sov_3f9a1c0b7e2d4a6f8c1e",
"redirect_uris": [
"https://analytics.acme.com/callback"
],
"scopes": [
"openid",
"profile",
"email"
],
"enabled": true,
"created_at": "2026-10-18T10:20:00+00:00",
"client_secret_set": true,
"client_secret": "q3Zr…(43 characters)"
}PATCH/api/v1/auth/oauth-apps/{app_id}
Change an OAuth app.
Path parameters
Body
Responses
curl -s -X PATCH "$URL/api/v1/auth/oauth-apps/7f2c…" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"enabled": false}'DELETE/api/v1/auth/oauth-apps/{app_id}
Remove an OAuth app; its tokens stop working.
Path parameters
Responses
curl -s -X DELETE "$URL/api/v1/auth/oauth-apps/7f2c…" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Token keys
GET/api/v1/auth/project-jwks
The public keys that sign the project's app-user tokens, to verify them in your own backend.
A JSON Web Key Set (ES256). Cache it; keys rotate with overlap, so look up the token's kid (Sessions and tokens).
Responses
curl -s "$URL/api/v1/auth/project-jwks" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"kid": "p.f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07.7c1e9a04",
"alg": "ES256",
"use": "sig"
}
]
}Console sign-in (SAML, GitHub, Google, Microsoft, CLI)
These sign people in to the Sovaria console itself, not to your app.
GET/api/v1/auth/saml/login
Start SAML single sign-on into the Sovaria console for an organization (redirects to your identity provider).
For console members of organizations with SAML set up (Team plan and above). The browser is redirected (302) to the identity provider; after sign-in it comes back to the console signed in.
Query parameters
Responses
curl -s "$URL/api/v1/auth/saml/login?org_id=b2f4a1c8-7d3e-4f0a-9c6b-5e8d2a1f3c90" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"GET/api/v1/auth/saml/metadata/{org_id}
The organization's SAML service-provider metadata (XML), to load into your identity provider.
Path parameters
Responses
curl -s "$URL/api/v1/auth/saml/metadata/b2f4a1c8-7d3e-4f0a-9c6b-5e8d2a1f3c90" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"GET/api/v1/auth/github/login
Sign in to the Sovaria console with GitHub, or connect GitHub to a project (browser redirect).
Redirects to GitHub. Afterwards the browser returns to redirect_uri with ?handoff=<one-time token> (valid 60 s), which the page exchanges with POST /api/v1/auth/oauth/handoff. With project_id it connects GitHub to that project; access=repo also allows writing to repositories.
Query parameters
Responses
curl -s "$URL/api/v1/auth/github/login?redirect_uri=https://console.sovaria.in/auth/callback" \
-H "apikey: $ANON_KEY"GET/api/v1/auth/google/login
Sign in to the Sovaria console with Google (browser redirect).
Returns to redirect_uri with ?handoff=<one-time token> to exchange with POST /api/v1/auth/oauth/handoff.
Query parameters
Responses
curl -s "$URL/api/v1/auth/google/login?redirect_uri=https://console.sovaria.in/auth/callback" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"GET/api/v1/auth/microsoft/login
Sign in to the Sovaria console with Microsoft (browser redirect).
Returns to redirect_uri with ?handoff=<one-time token> to exchange with POST /api/v1/auth/oauth/handoff.
Query parameters
Responses
curl -s "$URL/api/v1/auth/microsoft/login?redirect_uri=https://console.sovaria.in/auth/callback" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"GET/api/v1/auth/github/connection-state
Whether GitHub is connected (for your account, or a project).
Query parameters
Responses
curl -s "$URL/api/v1/auth/github/connection-state" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"provider": "github",
"project_scope": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"connected": true,
"connected_at": "2026-10-04T15:20:00Z",
"updated_at": "2026-10-04T15:20:00Z",
"token_hint": "gho_…1f",
"metadata": {
"email": "dev@acme.com",
"login": "acme-dev",
"name": "Acme Dev",
"scopes": [
"read:user",
"repo",
"user:email"
]
}
}DELETE/api/v1/auth/github/connection-state
Disconnect GitHub.
Query parameters
Responses
curl -s -X DELETE "$URL/api/v1/auth/github/connection-state" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"provider": "github",
"project_scope": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"connected": false,
"disconnected": true
}POST/api/v1/auth/cli/oauth/authorize
Approve a sov login request from the browser (the console's CLI consent page calls this).
The signed-in console user approves the CLI's PKCE request; the answer is the CLI's loopback URL with a one-time code and the state, which the CLI exchanges at POST /oauth/token.
Body
Returns
Responses
curl -s -X POST "$URL/api/v1/auth/cli/oauth/authorize" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"client_id": "sovaria-cli", "redirect_uri": "http://127.0.0.1:54321/callback", "code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", "state": "af0ifjsldkj"}'Response
{
"redirect_url": "http://127.0.0.1:54321/callback?code=4f1c…&state=af0ifjsldkj"
}Older paths
These answer the same as the project-scoped paths above; new code should use those.
GET/api/v1/auth/product-users
List the project's app users, newest first — the same as /api/v1/projects/{project_id}/auth/users, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Query parameters
Returns
Responses
curl -s "$URL/api/v1/auth/product-users?limit=50&status=active" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
[
{
"id": "a71d0c5e-2f84-4b9a-9e13-6c5d8f2b1a47",
"node_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"email": "ana@example.com",
"is_active": true,
"created_at": "2026-10-02T08:14:22.481Z",
"updated_at": "2026-10-16T19:03:51.112Z",
"mfa_enabled": false,
"phone_mfa_enabled": false,
"any_mfa_enabled": false,
"last_sign_in_at": "2026-10-17T09:41:05.330Z",
"roles": [
"staff"
],
"role_sources": [
"project_settings"
],
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07"
}
]POST/api/v1/auth/product-users
Create an app user (as an admin) — the same as /api/v1/projects/{project_id}/auth/users, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Body
Returns
Responses
curl -s -X POST "$URL/api/v1/auth/product-users" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "Ana Lima", "email": "ana@example.com", "roles": ["staff"], "send_magic_link": true}'Response
{
"id": "a71d0c5e-2f84-4b9a-9e13-6c5d8f2b1a47",
"node_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"email": "ana@example.com",
"is_active": true,
"created_at": "2026-10-02T08:14:22.481Z",
"updated_at": "2026-10-16T19:03:51.112Z",
"mfa_enabled": false,
"phone_mfa_enabled": false,
"any_mfa_enabled": false,
"last_sign_in_at": null,
"roles": [
"staff"
],
"role_sources": [
"project_settings"
],
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"actor_lane": "product",
"auth_subject_type": "product_user",
"pending_invite": true
}DELETE/api/v1/auth/product-users/{user_id}
Delete an app user permanently and sign them out everywhere — the same as /api/v1/projects/{project_id}/auth/users/{user_id}, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Responses
curl -s -X DELETE "$URL/api/v1/auth/product-users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"POST/api/v1/auth/product-users/{user_id}/ban
Ban an app user, or unban them — each call switches it — the same as /api/v1/projects/{project_id}/auth/users/{user_id}/ban, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Responses
curl -s -X POST "$URL/api/v1/auth/product-users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/ban" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"is_active": false
}GET/api/v1/auth/product-users/{user_id}/roles
A user's app roles — the same as /api/v1/projects/{project_id}/auth/users/{user_id}/roles, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Responses
curl -s "$URL/api/v1/auth/product-users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/roles" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"org_id": "b2f4a1c8-7d3e-4f0a-9c6b-5e8d2a1f3c90",
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"roles": [
"staff"
]
}PUT/api/v1/auth/product-users/{user_id}/roles
Replace a user's app roles — the same as /api/v1/projects/{project_id}/auth/users/{user_id}/roles, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Body
Responses
curl -s -X PUT "$URL/api/v1/auth/product-users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/roles" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"roles": ["staff", "billing"]}'Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"org_id": "b2f4a1c8-7d3e-4f0a-9c6b-5e8d2a1f3c90",
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"roles": [
"billing",
"staff"
]
}GET/api/v1/auth/product-users/{user_id}/mfa
Whether a user has two-factor sign-in turned on — the same as /api/v1/projects/{project_id}/auth/users/{user_id}/mfa, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Returns
Responses
curl -s "$URL/api/v1/auth/product-users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/mfa" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"mfa_enabled": true,
"phone_mfa_enabled": false,
"totp_enrolled": true,
"any_mfa_enabled": true
}POST/api/v1/auth/product-users/{user_id}/mfa/reset
Turn off a user's two-factor sign-in (they lost their phone) — the same as /api/v1/projects/{project_id}/auth/users/{user_id}/mfa/reset, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Responses
curl -s -X POST "$URL/api/v1/auth/product-users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/mfa/reset" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"status": "reset",
"message": "MFA has been reset for this user",
"mfa_enabled": false,
"phone_mfa_enabled": false,
"totp_enrolled": false,
"any_mfa_enabled": false
}GET/api/v1/auth/users
Older listing of app users — use GET /api/v1/projects/{project_id}/auth/users.
Query parameters
Returns
Responses
curl -s "$URL/api/v1/auth/users?limit=50&status=active" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
[
{
"id": "a71d0c5e-2f84-4b9a-9e13-6c5d8f2b1a47",
"node_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"email": "ana@example.com",
"is_active": true,
"created_at": "2026-10-02T08:14:22.481Z",
"updated_at": "2026-10-16T19:03:51.112Z",
"mfa_enabled": false,
"phone_mfa_enabled": false,
"any_mfa_enabled": false,
"last_sign_in_at": "2026-10-17T09:41:05.330Z",
"roles": [
"staff"
],
"role_sources": [
"project_settings"
],
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07"
}
]DELETE/api/v1/auth/users/{user_id}
Delete an app user permanently and sign them out everywhere — the same as /api/v1/projects/{project_id}/auth/users/{user_id}, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Responses
curl -s -X DELETE "$URL/api/v1/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"POST/api/v1/auth/users/{user_id}/ban
Ban an app user, or unban them — each call switches it — the same as /api/v1/projects/{project_id}/auth/users/{user_id}/ban, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Responses
curl -s -X POST "$URL/api/v1/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/ban" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"is_active": false
}GET/api/v1/auth/users/{user_id}/roles
A user's app roles — the same as /api/v1/projects/{project_id}/auth/users/{user_id}/roles, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Responses
curl -s "$URL/api/v1/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/roles" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"org_id": "b2f4a1c8-7d3e-4f0a-9c6b-5e8d2a1f3c90",
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"roles": [
"staff"
]
}PUT/api/v1/auth/users/{user_id}/roles
Replace a user's app roles — the same as /api/v1/projects/{project_id}/auth/users/{user_id}/roles, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Body
Responses
curl -s -X PUT "$URL/api/v1/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/roles" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"roles": ["staff", "billing"]}'Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"org_id": "b2f4a1c8-7d3e-4f0a-9c6b-5e8d2a1f3c90",
"project_id": "f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07",
"roles": [
"billing",
"staff"
]
}GET/api/v1/auth/users/{user_id}/mfa
Whether a user has two-factor sign-in turned on — the same as /api/v1/projects/{project_id}/auth/users/{user_id}/mfa, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Returns
Responses
curl -s "$URL/api/v1/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/mfa" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"mfa_enabled": true,
"phone_mfa_enabled": false,
"totp_enrolled": true,
"any_mfa_enabled": true
}POST/api/v1/auth/users/{user_id}/mfa/reset
Turn off a user's two-factor sign-in (they lost their phone) — the same as /api/v1/projects/{project_id}/auth/users/{user_id}/mfa/reset, with the project taken from the project URL or the X-Sovaria-Project-ID header.
Path parameters
Responses
curl -s -X POST "$URL/api/v1/auth/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/mfa/reset" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"status": "reset",
"message": "MFA has been reset for this user",
"mfa_enabled": false,
"phone_mfa_enabled": false,
"totp_enrolled": false,
"any_mfa_enabled": false
}