API Reference
Authentication
Register, log in, manage your session, and recover your password. The API uses Laravel Sanctum bearer tokens for protected routes.
/api/register
Register a new account
Creates a new user and immediately returns a Sanctum access token.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
name |
string | Required | Full name. Max 255 characters. |
email |
string | Required | Must be a valid, unique email address. |
password |
string | Required | Minimum 8 characters. |
password_confirmation |
string | Required | Must match password. |
Request body example
{
"name": "Jane Doe",
"email": "[email protected]",
"password": "supersecret123",
"password_confirmation": "supersecret123"
}
Responses
{
"access_token": "1|abc123def456...",
"token_type": "Bearer",
"user": {
"id": 1,
"name": "Jane Doe",
"email": "[email protected]"
},
"email_verified": false,
"email_verified_at": null
}
{
"message": "The given data was invalid.",
"errors": {
"email": ["The email has already been taken."],
"password": ["The password must be at least 8 characters."]
}
}
/api/login
Log in
Authenticates a user with email and password and returns a Sanctum access token.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
email |
string | Required | The user's email address. |
password |
string | Required | The user's password. |
Responses
{
"access_token": "1|abc123def456...",
"token_type": "Bearer",
"user": {
"id": 1,
"name": "Jane Doe",
"email": "[email protected]"
},
"email_verified": false,
"email_verified_at": null
}
{
"message": "The provided credentials are incorrect.",
"errors": {
"email": ["The provided credentials are incorrect."]
}
}
/api/me
Get current user
Returns the authenticated user's profile. Requires a valid bearer token.
Responses
{
"id": 1,
"name": "Jane Doe",
"email": "[email protected]",
"google_id": null,
"avatar": null,
"email_verified_at": "2026-06-04T09:12:33Z",
"reminder_time": "21:10",
"reminder_last_sent_at": "2026-06-05T21:10:11Z"
}
{ "message": "Unauthenticated." }
/api/logout
Log out
Revokes the bearer token used for the current request. Requires authentication.
Responses
{ "message": "Successfully logged out" }
/api/forgot-password
Request a password reset link
Sends a password-reset email to the given address if the user exists.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
email |
string | Required | The user's email address. |
Responses
{ "message": "We have e-mailed your password reset link!" }
{
"errors": {
"email": ["We can't find a user with that e-mail address."]
}
}
/api/reset-password
Reset password
Finalises a password reset using the token emailed by the forgot-password endpoint.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | Required | Reset token from the password reset email. |
email |
string | Required | Email of the account being reset. |
password |
string | Required | The new password (min 8 chars). |
password_confirmation |
string | Required | Must match password. |
Responses
{ "message": "Your password has been reset!" }
{
"message": "The given data was invalid.",
"errors": {
"email": ["This password reset token is invalid."]
}
}
/api/auth/google
Redirect to Google
Returns a JSON payload containing the Google OAuth redirect URL. The frontend should send the user to that URL to begin the sign-in flow.
Response
{
"url": "https://accounts.google.com/o/oauth2/auth?client_id=...&redirect_uri=...&response_type=code&scope=openid%20profile%20email"
}
/api/auth/google/callback
Google OAuth callback
Handled automatically by Google. On success the user is redirected to FRONTEND_URL/login?token=...&user=.... On failure they are redirected with an error query string.
Response
HTTP 302 redirect. Body is empty.
Email verification opt-in
The User model implements MustVerifyEmail and a verification
email is queued automatically on register. Verification is
not required to use the API — unverified users can list accounts,
log transactions, and read summaries. The only endpoints that require a verified email
are the sensitive bulk writers:
POST /api/transactions/sync— offline batch uploadPOST /api/import/store— generic CSV importPOST /api/import/kuda/store— Kuda bank-statement import
Those routes return a 403 with { "requires_verified_email": true } — see the Errors page for the shape.
Google OAuth auto-flips email_verified_at on first login, so users who sign in with Google never see the verification banner.
/api/email/verify/{id}/{hash}
Verify email address
The click-target of the verification email. The URL is signed and must
be presented exactly as it appears in the message — it is the proof that the user
controls the inbox. On success the server marks the address verified, fires the
Verified event, and 302-redirects to FRONTEND_URL/dashboard?verified=1
(or ?verified=already if the address was already verified).
The frontend should send users to this URL with no extra wrapping — the SPA doesn't need to call it with Authorization headers. Just navigate the browser.
Responses
HTTP/1.1 302 Found
Location: https://app.yourdomain.com/dashboard?verified=1
HTTP/1.1 403 Forbidden
Invalid or expired verification link.
HTTP/1.1 404 Not Found
/api/email/verification-notification
Resend verification email
Queues a fresh verification email for the authenticated user. Throttled to 6 requests per minute per token. The notification is sent from the VerifyEmailNotification class and uses the VerifyEmail mail template.
Responses
{
"message": "Verification email queued.",
"email_verified": false,
"verification_sent_at": "2026-06-06T21:10:11+00:00"
}
If the user is already verified, the response short-circuits to { "message": "Email already verified.", "email_verified": true }.
{ "message": "Too Many Attempts." }
/api/email/verification-status
Get verification status
Lightweight endpoint for the SPA to decide whether to render the verification banner. Cheaper than /me if you don't need the full profile.
Response
{
"email_verified": true,
"email": "[email protected]"
}
/api/auth/check-email?email={email}
Check email availability
Public, rate-limited probe used by the register / login screens to show a “Sign in instead” / “Register instead” hint. Always responds with a 200 and the same shape — the response never reveals whether the email is registered, which is the standard mitigation against user-enumeration attacks.
Response
{ "available": true }