Authentication
DocSnap uses JWT (JSON Web Tokens) for authentication. Access tokens are short-lived (1 hour) and refresh tokens are long-lived (30 days).
Token Architecture
| Token | Lifetime | Purpose |
|---|---|---|
| Access Token | 1 hour | Authenticates API requests via Authorization: Bearer header |
| Refresh Token | 30 days | Obtains new access tokens without re-entering credentials |
Both tokens are HS256 JWTs. The access token payload:
{
"sub": "user-uuid",
"type": "access",
"iat": 1771709471,
"exp": 1771713071
}
POST /v1/auth/register
Create a new user account. A verification email is sent asynchronously.
Request:
{
"email": "driver@acmefreight.com",
"password": "securepass123"
}
Response (201):
{
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "driver@acmefreight.com",
"email_verified": false
},
"message": "Account created. Please check your email to verify your address."
}
No tokens are returned at registration. The user must verify their email first.
POST /v1/auth/login
Authenticate with email and password. Optionally includes device trust verification.
Request:
{
"email": "driver@acmefreight.com",
"password": "securepass123",
"device_id": "20C85E1B-3B11-43E3-821C-8C2CC27998BD",
"device_name": "iPhone"
}
device_id and device_name are optional. When provided:
- First device after email verification is auto-trusted
- Subsequent new devices require email confirmation (returns 403)
- Already-trusted devices proceed normally
Response (200):
{
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "driver@acmefreight.com",
"email_verified": true
},
"access_token": "eyJ...",
"refresh_token": "eyJ..."
}
Error Responses:
| Status | Code | Condition |
|---|---|---|
| 401 | INVALID_CREDENTIALS | Wrong email or password |
| 403 | EMAIL_NOT_VERIFIED | Email not yet verified |
| 403 | DEVICE_NOT_CONFIRMED | New device needs email confirmation |
POST /v1/auth/refresh
Exchange a refresh token for a new access token.
Request:
{
"refresh_token": "eyJ..."
}
Response (200):
{
"access_token": "eyJ..."
}
POST /v1/auth/verify-email
Verify email address using the token from the verification email.
Request:
{
"token": "raw-token-from-email-link"
}
Response (200):
{
"user": { "id": "uuid", "email": "driver@acmefreight.com", "email_verified": true },
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"message": "Email verified successfully."
}
POST /v1/auth/verification-status
Poll whether an email address has been verified. Used by clients while showing a "check your email" screen.
Request:
{
"email": "driver@acmefreight.com"
}
Response (200) -- verified:
{
"verified": true,
"user": { "id": "uuid", "email": "driver@acmefreight.com", "email_verified": true },
"access_token": "eyJ...",
"refresh_token": "eyJ..."
}
Response (200) -- not yet verified:
{
"verified": false
}
Always returns verified: false for non-existent emails (prevents enumeration).
POST /v1/auth/resend-verification
Resend the verification email. Rate limited: 1/min, 5/hour.
Request:
{
"email": "driver@acmefreight.com"
}
Response (200):
{
"message": "If an account exists with this email, a verification link has been sent."
}
POST /v1/auth/confirm-device
Confirm a new device using the token from the confirmation email.
Request:
{
"token": "raw-token-from-email-link"
}
Response (200):
{
"user": { "id": "uuid", "email": "driver@acmefreight.com", "email_verified": true },
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"message": "Device confirmed successfully."
}
POST /v1/auth/device-status
Poll whether a device has been confirmed.
Request:
{
"email": "driver@acmefreight.com",
"device_id": "20C85E1B-3B11-43E3-821C-8C2CC27998BD"
}
Response (200) -- confirmed:
{
"confirmed": true,
"user": { "id": "uuid", "email": "driver@acmefreight.com", "email_verified": true },
"access_token": "eyJ...",
"refresh_token": "eyJ..."
}
Response (200) -- not yet confirmed:
{
"confirmed": false
}
POST /v1/auth/forgot-password
Send a password reset email. Rate limited: 1/min, 5/hour.
Request:
{
"email": "driver@acmefreight.com"
}
Response (200):
{
"message": "If an account exists with this email, a password reset link has been sent."
}
POST /v1/auth/reset-password
Reset password using the token from the reset email.
Request:
{
"token": "raw-token-from-email-link",
"password": "newSecurePass456"
}
Response (200):
{
"message": "Password has been reset successfully. You can now log in."
}
GET /v1/me
Get the authenticated user's profile.
Headers: Authorization: Bearer <access_token>
Response (200):
{
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "driver@acmefreight.com"
}
}