Auth API

building

Sign somebody in with a code or a password, read the session back, and end it.

Sign in

Two ways in, and every account has the first one.

A code always works. It is how accounts are created, so every user has it without setting anything up. A password works if they have set one, which is optional — see sign up.

If you want the flow people expect from Google and everybody else, ask for the address first, then ask for whatever that account can answer. Your app knows which by trying a password sign-in and reading the error, or simply by offering both.

POST/v1/auth/sign-in/email-otp

The code path, in two calls — the same pair that creates an account. Ask for a code with send-verification-otp, then:

Request
1curl -X POST "$BASE/v1/auth/sign-in/email-otp" \2  -H "authorization: Bearer $PK" \3  -H "content-type: application/json" \4  -d '{"email":"ada@example.com","otp":"481920"}'

Same response shape as below: a session cookie and a token. Full flow in sign up, because for a new address it is the sign-up.

POST/v1/auth/sign-in/email

Takes a publishable key.

Request
1curl -X POST "$BASE/v1/auth/sign-in/email" \2  -H "authorization: Bearer $PK" \3  -H "content-type: application/json" \4  -d '{"email":"ada@example.com","password":"a-long-password"}'
Response
1{2  "redirect": false,3  "token": "onSnRF8wZMuCs03Li7lcKJT2tLTvHjQO",4  "user": { "id": "Nrzmwy…", "email": "ada@example.com", "emailVerified": true }5}

The session arrives as a Set-Cookie. The token in the body is the same session for clients that cannot hold cookies — a native app, say.

If the account has two-factor enabled, no session comes back. You get this instead, and send them to your second-factor screen:

json
{ "twoFactorRedirect": true, "twoFactorMethods": ["totp"] }

See two-factor for the call that finishes it.

GET/v1/auth/get-session

Who the caller is, or null. This is the call your app makes on load.

Request
1curl "$BASE/v1/auth/get-session" \2  -H "authorization: Bearer $PK" \3  -b "session cookie"
Response
1{2  "session": {3    "id": "h0bsPL9wBPaRfvslAcU2q0w8VPeF4wJu",4    "expiresAt": "2026-08-26T14:22:39.655Z",5    "activeOrganizationId": null6  },7  "user": { "id": "Nrzmwy…", "email": "ada@example.com", "emailVerified": true }8}

A missing or expired session answers 200 with null rather than 401. Not being signed in is an ordinary state for this call, not a failure.

POST/v1/auth/sign-out
Request
1curl -X POST "$BASE/v1/auth/sign-out" \2  -H "authorization: Bearer $PK" \3  -b "session cookie"

Ends the session it was called with and clears the cookie. Other sessions that person has — another browser, their phone — are left alone.

Errors

StatusCodeMeans
401INVALID_EMAIL_OR_PASSWORDWrong password, no password set, or no such account
400INVALID_OTPWrong or expired code
403EMAIL_NOT_VERIFIEDThe address is unconfirmed — always required, see settings
403—Publishable key used from an unregistered origin
429—3 attempts per 10 seconds per IP; see limits
503—The project's auth database is still being created

One message covers a wrong password, an account with no password, and an unknown address, on purpose. Anything more specific turns this form into a way to test who has an account with you — and "that account has no password" would say a great deal about somebody who never chose to tell you.

EMAIL_NOT_VERIFIED re-sends the confirmation link as it refuses, so the way out is already in their inbox by the time they read the error. New accounts never see it — they were created by proving the address — so it only shows up for accounts that predate that change.