Authentication
The API exposes two authentication surfaces.
Public clients use the stable /api/v1/* JWT endpoints documented on this page. Browser-based and server-rendered flows can also use Better Auth session endpoints mounted at /api/auth/*.
Phone OTP is supported only through the versioned /api/v1/auth/phone/* routes on this page. The underlying Better Auth phone endpoints are not exposed as part of the public /api/auth/* surface.
Protected routes that use the app-user auth middleware accept either of the following credentials:
Authorization: Bearer <jwt>from the/api/v1/*auth endpoints- A valid Better Auth session cookie issued by
/api/auth/*
These credentials resolve identities from app_users only. Admin users continue to authenticate through the Laravel/Filament admin surface and are not valid callers for the public /api/v1/auth/*, /api/auth/*, or /api/v1/me app-user routes.
For account creation and onboarding, see Registration.
Versioned JWT Routes
Login
POST /api/v1/auth/loginRequest body:
{
"email": "[email protected]",
"password": "your-password"
}Success response:
{
"data": {
"token": "<bearer-token>",
"expiresAt": "<iso8601-expires-at>",
"user": {
"id": "usr_123",
"email": "[email protected]",
"name": "Jane Doe",
"type": "user"
}
}
}Errors
401 invalid_credentialswhen the email or password is invalid, missing, malformed, or incorrect429 rate_limitedafter too many login attempts from the same resolved client key; if trusted proxy IP headers are disabled or unavailable, login throttling falls back to a shared bucket instead of trusting forwarded IP headers
Guest Session
POST /api/v1/auth/guestCreate an anonymous guest identity for read-only flows before account creation.
Response
{
"data": {
"token": "<bearer-token>",
"expiresAt": "<iso8601-expires-at>",
"user": {
"id": "guest_<uuid>",
"type": "guest"
}
}
}Guest tokens expire after seven days.
Using Authenticated Routes
Send JWTs with the Authorization header when calling protected /api/v1/* routes:
Authorization: Bearer <token>If your client already has a Better Auth browser session, the same middleware can resolve the authenticated app user from the session cookie without a bearer token.
Refresh Current JWT
POST /api/v1/auth/refresh
Authorization: Bearer <token>Refresh is a sliding-window JWT refresh endpoint. It accepts a valid guest or user bearer token and returns a new token with a fresh expiry.
Response
{
"data": {
"token": "<new-bearer-token>",
"expiresAt": "<iso8601-expires-at>"
}
}Errors
401 auth_requiredwhen the bearer token is missing, expired, malformed, or refers to a deleted user
Current User
Read the Current User
GET /api/v1/meThis route returns the current app-user identity from either a bearer JWT or a Better Auth session cookie. The handle field is omitted until the user claims one.
Response for a User Session
{
"data": {
"user": {
"id": "usr_123",
"email": "[email protected]",
"name": "Jane Doe",
"handle": "janedoe",
"type": "user",
"createdAt": "<iso8601-created-at>"
}
}
}Response for a Guest Session
{
"data": {
"user": {
"id": "guest_123",
"type": "guest"
}
}
}Errors
401 auth_requiredwhen no valid bearer token or session cookie is present, or the authenticated user no longer exists
Update the Current User
PATCH /api/v1/meThis route partially updates name and handle for the current authenticated user. It accepts a user bearer JWT or a Better Auth session cookie.
Request Body
{
"name": "Jane Doe",
"handle": "janedoe"
}All fields are optional. Omitted fields remain unchanged, and handle values are normalized to lowercase before persistence.
Response
{
"data": {
"user": {
"id": "usr_123",
"email": "[email protected]",
"name": "Jane Doe",
"handle": "janedoe",
"type": "user",
"createdAt": "<iso8601-created-at>"
}
}
}Errors
400 invalid_bodywhen the request body is malformed JSON or a JSON value other than an object401 auth_requiredwhen no valid bearer token or session cookie is present, or the authenticated user no longer exists403 forbiddenwhen the caller is a guest identity409 handle_takenwhen the requested handle belongs to another user422 validation_errorwhennameorhandlefails validation
Current Scope
The following auth endpoints are implemented today:
POST /api/v1/auth/registerPOST /api/v1/auth/loginPOST /api/v1/auth/guestPOST /api/v1/auth/refreshPOST /api/v1/auth/phone/send-otpPOST /api/v1/auth/phone/verify-otpPOST /api/v1/auth/phone/resend-otpGET /api/v1/mePATCH /api/v1/me
Social OAuth flows use Better Auth native routes (not versioned /api/v1/auth/*):
POST /api/auth/sign-in/social— initiate Google or Facebook OAuthGET /api/auth/callback/google— Google OAuth callback (handled by Better Auth)GET /api/auth/callback/facebook— Facebook OAuth callback (handled by Better Auth)POST /api/auth/sign-up/email— Better Auth email sign-upPOST /api/auth/sign-in/email— Better Auth email sign-inGET /api/auth/get-session— Better Auth session lookup
Logout, account deletion, and additional provider-specific public routes are not part of the documented /api/v1/* contract yet.
Social OAuth
Better Auth handles Google and Facebook sign-in at the /api/auth/* surface. Clients should use the Better Auth SDK or follow the standard OAuth 2.0 flow.
Initiate Sign-In
POST /api/auth/sign-in/social{
"provider": "google",
"callbackURL": "/dashboard"
}Replace "google" with "facebook" to use the Facebook provider. Both providers return a redirect URL that the client must follow to complete the OAuth handshake.
Provider Setup
Configure the provider app to allow the Better Auth callback routes generated from BETTER_AUTH_URL:
- Google:
http://localhost:3001/api/auth/callback/google - Facebook:
http://localhost:3001/api/auth/callback/facebook
For deployed environments, replace http://localhost:3001 with the API's public BETTER_AUTH_URL so the provider redirect URI exactly matches the callback route Better Auth serves.
Response
{
"url": "https://accounts.google.com/o/oauth2/auth?...",
"redirect": true
}After the OAuth handshake completes, Better Auth establishes a session. Authenticated /api/v1/* routes accept either a Better Auth session cookie or a bearer JWT issued by the /api/v1/auth/* endpoints.
Phone OTP
Phone-number based authentication uses a one-time code delivered via SMS. The flow is:
- Call
send-otpto deliver a code to the user's phone. - Call
verify-otpwith the code to authenticate and receive a bearer token. - Use
resend-otpif the user needs the code resent (rate-limited identically tosend-otp).
Send OTP
POST /api/v1/auth/phone/send-otp
Content-Type: application/json{
"phoneNumber": "+17678901234"
}Responses: 200 { data: { sent: true } } | 400 invalid_body | 422 validation_error | 429 rate_limited
Verify OTP
POST /api/v1/auth/phone/verify-otp
Content-Type: application/json{
"phoneNumber": "+17678901234",
"code": "123456"
}Responses: 200 { data: { token, expiresAt, user } } | 400 invalid_body | 401 invalid_otp | 422 validation_error | 429 rate_limited
Resend OTP
POST /api/v1/auth/phone/resend-otp
Content-Type: application/json{
"phoneNumber": "+17678901234"
}Responses: 200 { data: { sent: true } } | 400 invalid_body | 422 validation_error | 429 rate_limited
Rate limits:
send-otpandresend-otpshare a 5 requests per 10 minute window.verify-otpallows 10 requests per 10 minutes. These limits use the same resolved client key as the other auth routes; when trusted proxy headers are disabled or unavailable, the limiter falls back to a shared bucket instead of trusting forwarded IP headers.