Skip to main content
The Auth API is GoTrue v2.184.0 mounted at /auth/v1/* on your project URL. Every endpoint accepts the project’s Anon Key (or, for admin endpoints, the Service Role Key) as both the apikey and Authorization: Bearer headers, except where noted (e.g., after sign-in, the user’s access token replaces the Anon Key in Authorization). For end-to-end signup/signin flows, see Signup, signin, magic link. For OAuth, see OAuth providers. For the conceptual model, see Auth model.

Common headers

Two header sets you’ll use depending on whether the user is signed in: Unauthenticated requests (signup, signin, recovery, OAuth initiation):
apikey: <Anon Publishable Key>
Authorization: Bearer <Anon Publishable Key>
Content-Type: application/json
Authenticated requests (/user, /logout, MFA enrollment, etc.):
apikey: <Anon Publishable Key>
Authorization: Bearer <user access token>
Content-Type: application/json
Admin requests (/admin/users/*):
apikey: <Service Role Secret Key>
Authorization: Bearer <Service Role Secret Key>
Content-Type: application/json

Signup

POST /auth/v1/signup

Create a new user with email + password (or phone + password if phone auth is enabled). On default autoConfirm: true, returns a session immediately. On autoConfirm: false, returns the user record only; the user must verify their email via the link sent by GoTrue.
email
string
Email address. Either email or phone is required.
phone
string
E.164 phone number (e.g., +14155552671). Requires GOTRUE_EXTERNAL_PHONE_ENABLED=true and an SMS provider configured.
password
string
required
At least 6 characters by GoTrue default.
data
object
Arbitrary key/value pairs to store in user_metadata. User-editable.
gotrue_meta_security
object
{ captcha_token: "..." } if CAPTCHA is enabled.
{
  "email": "alice@example.com",
  "password": "correcthorsebatterystaple",
  "data": { "display_name": "Alice" }
}
requests.post(
    f"{BASE_URL}/auth/v1/signup",
    headers={"apikey": ANON_KEY, "Authorization": f"Bearer {ANON_KEY}", "Content-Type": "application/json"},
    json={"email": "alice@example.com", "password": "correcthorsebatterystaple"},
)
Response (autoConfirm: true):
{
  "access_token": "eyJ...",
  "token_type": "bearer",
  "expires_in": 3600,
  "expires_at": 1748563200,
  "refresh_token": "v1.MzQ1...",
  "user": {
    "id": "...",
    "aud": "authenticated",
    "role": "authenticated",
    "email": "alice@example.com",
    "user_metadata": {"display_name": "Alice"},
    "app_metadata": {"provider": "email", "providers": ["email"]},
    "created_at": "2026-05-29T00:00:00Z"
  }
}
Response (autoConfirm: false): the user object only, no tokens.

Signin

POST /auth/v1/token?grant_type=password

Exchange email/password (or phone/password) for an access token + refresh token.
grant_type
string
required
Must be password.
email
string
Either email or phone is required.
phone
string
E.164 format. Requires phone auth enabled.
password
string
required
requests.post(
    f"{BASE_URL}/auth/v1/token",
    headers={"apikey": ANON_KEY, "Authorization": f"Bearer {ANON_KEY}", "Content-Type": "application/json"},
    params={"grant_type": "password"},
    json={"email": "alice@example.com", "password": "..."},
)
Response: same shape as signup with autoConfirm: true.

POST /auth/v1/token?grant_type=refresh_token

Exchange a refresh token for a new access token + new refresh token. Refresh-token rotation is enabled by default — each refresh token is single-use, with a 10-second grace window for concurrent refresh attempts.
refresh_token
string
required
requests.post(
    f"{BASE_URL}/auth/v1/token",
    headers={"apikey": ANON_KEY, "Authorization": f"Bearer {ANON_KEY}", "Content-Type": "application/json"},
    params={"grant_type": "refresh_token"},
    json={"refresh_token": "v1.MzQ1..."},
)

POST /auth/v1/token?grant_type=pkce

Exchange a PKCE authorization code (from OAuth or magic link) for a session.
auth_code
string
required
Authorization code from the redirect query string.
code_verifier
string
required
The PKCE verifier you generated before calling /authorize.
See OAuth providers for the full PKCE walkthrough.

POST /auth/v1/otp

Send a one-time email (or SMS) with a sign-in link. The user clicks it, GoTrue redirects them back to your app with tokens in the URL fragment.
email
string
Either email or phone is required.
phone
string
E.164 format. Requires phone auth enabled.
create_user
boolean
Default true. When false, returns 400 if no user with that address exists — useful for “magic link only for existing users.”
options
object
{ email_redirect_to: "...", data: { ... } }email_redirect_to overrides the default site URL for this request; data populates user_metadata if create_user results in a new user.
{
  "email": "alice@example.com",
  "create_user": true,
  "options": { "email_redirect_to": "https://your-app.example.com/auth/callback" }
}
requests.post(
    f"{BASE_URL}/auth/v1/otp",
    headers={"apikey": ANON_KEY, "Authorization": f"Bearer {ANON_KEY}", "Content-Type": "application/json"},
    json={"email": "alice@example.com", "create_user": True, "options": {"email_redirect_to": "..."}},
)
Response: 200 OK with empty body if the email was queued. Returns 200 even for non-existent addresses when create_user: false would have failed — GoTrue treats this as “do not enumerate users.”

POST /auth/v1/verify

Verify an OTP token directly (alternative to clicking the email link). Useful for native apps that handle the email link via a URL scheme.
type
string
required
signup, magiclink, recovery, invite, email_change, sms, or phone_change.
token
string
required
The token from the email link or SMS.
email
string
Required for email-typed verifications.
phone
string
Required for SMS verifications.
Response: session tokens, same shape as POST /token?grant_type=password.

Password recovery

POST /auth/v1/recover

Send a password-reset email. The user clicks it and arrives at your redirect_to URL signed in (tokens in URL fragment), then calls PUT /user to set a new password.
email
string
required
options
object
{ redirect_to: "https://your-app.example.com/auth/reset-password" } overrides the default site URL for this request.
requests.post(
    f"{BASE_URL}/auth/v1/recover",
    headers={"apikey": ANON_KEY, "Authorization": f"Bearer {ANON_KEY}", "Content-Type": "application/json"},
    json={"email": "alice@example.com", "options": {"redirect_to": "..."}},
)
Always returns 200, even if the email doesn’t exist — prevents user enumeration.

OAuth

GET /auth/v1/authorize

Initiates an OAuth flow. Returns a 302 redirect to the upstream provider. Not callable from cURL in the normal sense — you put the user’s browser at this URL.
provider
string
required
One of: apple, azure, bitbucket, discord, facebook, figma, github, gitlab, google, kakao, keycloak, linkedin_oidc, notion, slack, slack_oidc, spotify, twitch, twitter, workos, zoom. Must be enabled on the project.
redirect_to
string
Where to redirect after the OAuth dance completes. Must be in the project’s uriAllowList.
scopes
string
Space-separated extra scopes to request from the provider.
code_challenge
string
PKCE code challenge (base64url-encoded SHA-256 of the verifier). Including this puts the flow in PKCE mode — the code comes back in the query string instead of the URL fragment.
code_challenge_method
string
Must be S256 when code_challenge is set.
Full walkthrough at OAuth providers.

GET /auth/v1/callback

GoTrue’s own OAuth callback. The upstream provider redirects to this URL after the user authorizes; GoTrue exchanges the code, finalizes the session, then 302s the browser to your redirect_to. You shouldn’t call this directly — it’s the URL you register with the provider.

User management (authenticated)

GET /auth/v1/user

Return the currently-signed-in user. Read from the access token’s claims plus a fresh lookup against auth.users.
requests.get(
    f"{BASE_URL}/auth/v1/user",
    headers={"apikey": ANON_KEY, "Authorization": f"Bearer {access_token}"},
)

PUT /auth/v1/user

Update the signed-in user’s email, password, phone, or user_metadata. Cannot modify app_metadata from here (use the admin API).
email
string
Triggers a confirmation email if changed. The new email isn’t active until confirmed; the session continues under the old email until then.
password
string
The new password.
phone
string
Same flow as email — sends a verification SMS.
data
object
Merged into user_metadata.
requests.put(
    f"{BASE_URL}/auth/v1/user",
    headers={"apikey": ANON_KEY, "Authorization": f"Bearer {access_token}", "Content-Type": "application/json"},
    json={"data": {"display_name": "Alice Smith"}},
)

POST /auth/v1/logout

Invalidate the current session (revokes the refresh token server-side). Optionally specify scope.
scope
string
global (default — revoke all refresh tokens for this user), local (just this session), or others (every session except this one).
requests.post(
    f"{BASE_URL}/auth/v1/logout",
    headers={"apikey": ANON_KEY, "Authorization": f"Bearer {access_token}"},
    params={"scope": "global"},
)

Multi-factor authentication

TOTP MFA is enabled by default; phone-based MFA requires GOTRUE_MFA_PHONE_ENROLL_ENABLED=true and Twilio configured. Up to 10 factors per user.

POST /auth/v1/factors

Enroll a new MFA factor. Returns a TOTP secret (and QR code URI) the user can add to their authenticator app.
factor_type
string
required
totp or phone.
friendly_name
string
User-visible label, e.g., “iPhone Authy”.
issuer
string
TOTP issuer field, e.g., your app name.

POST /auth/v1/factors//challenge

Start a challenge (TOTP doesn’t need this; phone sends the OTP).

POST /auth/v1/factors//verify

Verify the challenge code. On first-time enrollment, also activates the factor. Returns an aal2 (Authenticator Assurance Level 2) access token if successful.

DELETE /auth/v1/factors/

Unenroll a factor. Requires the current access token to be at aal2 if the factor being deleted is the user’s last factor — prevents lockout from a stolen access token.

Admin (service-role only)

These require the Service Role Key, not the user’s access token. Server-side use only.

GET /auth/v1/admin/users

List all users in the project. Supports pagination via page and per_page (default 50).

POST /auth/v1/admin/users

Create a user with any email, password, metadata. Skips confirmation. Useful for migrations.
email
string
phone
string
password
string
email_confirm
boolean
Default true.
phone_confirm
boolean
Default true.
user_metadata
object
app_metadata
object
The only way to set app_metadata.
role
string
Override the default authenticated role.

GET /auth/v1/admin/users/

Fetch a specific user, including the fields GET /user doesn’t return (banned status, last sign-in, MFA factors, identities).

PUT /auth/v1/admin/users/

Update any field on the user, including app_metadata, ban_duration, and role. The right place to set custom claims that policies will read via auth.jwt().
email
string
phone
string
password
string
email_confirm
boolean
phone_confirm
boolean
user_metadata
object
app_metadata
object
ban_duration
string
24h, 48h, none, etc. Banned users get 401 on sign-in.

DELETE /auth/v1/admin/users/

Hard-delete a user. Cascades through identities, sessions, MFA factors, and (if configured) the auth.users row’s downstream references in public.*.

POST /auth/v1/admin/generate_link

Generate a magic-link URL without sending an email. Useful for impersonation flows or custom email delivery.
type
string
required
signup, invite, magiclink, recovery, email_change_current, email_change_new.
email
string
required
Response: { "action_link": "...", "email_otp": "...", "hashed_token": "...", ... }.

Error Responses

GoTrue returns errors as {"error": "code", "error_description": "human-readable"} or {"msg": "..."} depending on the endpoint. Common codes:
StatusCodeWhen
400invalid_grantWrong email/password, expired refresh token, or refresh-token already used
400email_not_confirmedSign-in attempt before email verification
400email_address_invalidMalformed email
400weak_passwordBelow 6 characters by default
400user_already_existsSignup with an existing email
401unauthorizedMissing or expired access token
403not_adminHitting /admin/* without the service role key
422invalid_credentialsSame as 400 invalid_grant in some GoTrue versions
429over_email_send_rate_limitToo many email-sending attempts (30/hour by default)
429over_sms_send_rate_limitToo many SMS attempts
429over_request_rate_limitToo many failed verify attempts
The /admin/* endpoints additionally return:
StatusCodeWhen
404user_not_foundUser UUID doesn’t exist
422validation_failedBody field shape is wrong (e.g., non-string phone)

Next steps

Signup, signin, magic link

End-to-end flows in Python, TypeScript, and cURL.

OAuth providers

Provider-specific configuration + PKCE.

Auth model

JWT structure, the four roles, refresh-token rotation, the autoconfirm default.

RLS Cookbook

How to use auth.uid() and auth.jwt() in policies on the tables your users will hit.