Authentication API
import { Badge } from ‘@astrojs/starlight/components’;
Authentication
Section titled “Authentication ”POST /api/auth/login
Section titled “POST /api/auth/login”Authenticate a user and receive a JWT.
Auth: None
Request:
{ "email": "doctor@clinicflow.lat", "password": "secure-password-here"}Response (200):
{ "token": "eyJhbGciOiJFUzI1NiIs...", "user": { "id": "uuid", "clinicId": "uuid", "email": "doctor@clinicflow.lat", "fullName": "Dr. García", "role": "admin", "subscriptionPlan": "pro", "subscriptionExpiresAt": "2026-07-01T00:00:00Z" }}Errors:
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR |
Email or password missing |
| 401 | INVALID_CREDENTIALS |
Wrong email or password |
| 403 | USER_INACTIVE |
User account deactivated |
| 403 | CLINIC_INACTIVE |
Clinic account deactivated |
Notes:
- Password is compared with bcrypt (cost factor 12)
- JWT payload includes
userId,clinicId,email,role,subscriptionPlan,subscriptionExpiresAt - Default expiry: 8 hours
JWT Structure
Section titled “JWT Structure”{ "sub": "user-uuid", "clinic_id": "clinic-uuid", "email": "doctor@clinicflow.lat", "app_role": "admin", "role": "authenticated", "plan": "pro", "exp": 1719700000, "iat": 1719671200}- Algorithm: ES256 (P-256 asymmetric)
- Expiry: 8 hours from issuance
- Live validation: RLS policies check
users.is_activeandclinics.is_activeon every query
POST /api/auth/register
Section titled “POST /api/auth/register”Self-service clinic registration. Requires a valid invite code.
Auth: None
Request:
{ "clinicName": "Clínica Dental San Miguel", "adminName": "Dr. García", "email": "doctor@example.com", "password": "secure-password-1234", "inviteCode": "ABC123DEF456"}Response (201):
{ "token": "eyJhbGciOiJFUzI1NiIs...", "clinic": { "id": "uuid", "name": "Clínica Dental San Miguel", "plan": "trial" }, "user": { "id": "uuid", "email": "doctor@example.com", "role": "admin" }}Errors:
| Status | Message |
|---|---|
400 |
Validation error (missing fields, invalid email, etc.) |
400 |
“Invite code is invalid, expired, or already used” |
409 |
“Email already registered” |
Notes:
- Invite codes are atomically claimed to prevent double-registration race conditions
- Admin password is hashed with bcrypt (cost factor 12)
- Clinic slug:
{name-lowercase-dashes}-{6-char-uuid-suffix}
GET /api/auth/me
Section titled “GET /api/auth/me”Get the current authenticated user’s profile.
Auth: Required
Response (200):
{ "id": "uuid", "email": "admin@clinica.com", "full_name": "Admin Name", "role": "admin", "clinic_id": "uuid", "clinic": { "name": "Clínica San Salvador", "slug": "clinica-san-salvador-abc123", "subscription_plan": "pro", "subscription_expires_at": "2025-12-31T00:00:00Z", "is_active": true }}Errors:
| Status | Message |
|---|---|
401 |
“Authentication token required” |
401 |
“Token expired” |
401 |
“Invalid token” |
402 |
“Subscription expired” (from requireActiveSubscription) |