User & Authentication API
Complete reference for authentication, profile, and user management endpoints.
OpenAPI tag groups: authentication, profile, users
Table of contents
- Overview
- User types & roles
- Authentication & authorization
- Authentication flow
- Common errors
- Field validation & constraints
- Query, filtering, sorting & pagination
- Authentication APIs (
/api/v3/auth) - Profile APIs (
/api/v3/users/profile) - User CRUD APIs (
/api/v3/user/v1/users) - Related events & side effects
Overview
| Area | Base path | Authentication | Authorization |
|---|---|---|---|
| Authentication | /api/v3/auth |
Mixed — public or validated JWT | Role checks on manager/staff endpoints only |
| Profile | /api/v3/users/profile |
Validated JWT access token (all routes) | Self only — authenticated user accesses own profile |
| User CRUD | /api/v3/user/v1/users |
Bearer header on most routes; no JWT validation | No role checks — list and health check are fully public |
JWT details
- Header:
Authorization: Bearer <access_token> - Access token lifetime: 5 minutes (production), 24 hours when
debug=true - Refresh token lifetime: 24 hours
- Algorithm: configured via
JWT_ALGORITHM(typically HS512)
User types & roles
| Name | user_type value |
Typical use |
|---|---|---|
SYSTEM_MANAGER |
100 |
Full admin |
MANAGER |
200 |
User management |
OFFICE_EMPLOYEE |
300 |
Office staff |
WAREHOUSE_EMPLOYEE |
400 |
Warehouse staff |
CUSTOMER |
1000 |
Self-registration default |
CUSTOMS_OFFICER |
2000 |
Customs role |
UNDEFINED |
9999 |
Fallback |
Manager-only auth endpoints require user_type 100 or 200 unless noted otherwise.
Create user (POST /auth/user/create) permissions:
| Caller role | Can create |
|---|---|
System Manager (100) |
Any role |
Manager (200) |
Any except System Manager |
Office Employee (300) |
Customer (1000) only |
Authentication & authorization
This section documents who can call each endpoint — authentication (proving identity) and authorization (permission to perform the action).
Authentication mechanisms
Two different Bearer dependencies are used in this API:
| Mechanism | Used by | Validates JWT | Validates role | Implementation |
|---|---|---|---|---|
| Validated JWT | /api/v3/auth (protected routes), /api/v3/users/profile |
Yes — signature, expiry, token_type=access |
Per-endpoint | JWTBearer (src/auth/bearer.py) |
| Bearer header only | /api/v3/user/v1/users (most routes) |
No — only checks Authorization: Bearer … is present |
No | OAuth2PasswordBearer (src/user/routers/user.py) |
| None | Public auth routes, list users, health check | — | — | No dependency |
Security note: User CRUD routes accept any non-empty Bearer token string without verifying it. Do not rely on these routes for production authorization until JWT validation is added.
Required header (when authentication applies):
Authorization: Bearer <access_token>Obtain <access_token> from POST /api/v3/auth/login. Use the access token, not the refresh token, for protected routes using validated JWT.
Account state required to obtain a token
Login (POST /api/v3/auth/login) succeeds only when all of the following are true:
| Condition | Failure |
|---|---|
| Email and password match | 401 — Invalid credentials |
is_active=true |
401 — User account is inactive |
is_banned=false |
401 — User account is banned |
email_verified=true |
401 — Email not verified |
Any role that passes login receives a JWT whose user_type claim reflects their role.
Role groups (authorization shorthand)
| Shorthand | user_type values |
Roles |
|---|---|---|
| Public | — | Unauthenticated callers |
| Any authenticated | 100, 200, 300, 400, 1000, 2000, 9999 | All roles with a valid access token |
| Manager+ | 100, 200 | System Manager, Manager |
| Staff+ | 100, 200, 300 | System Manager, Manager, Office Employee |
| Self | Any authenticated | Caller may only act on their own account (JWT user_id) |
Roles not listed on a restricted endpoint receive 403 Forbidden.
Operation types
| Operation | Meaning in this API |
|---|---|
| Execute | Trigger a one-off action (login, logout, send activation code, log login event) |
| Read | Retrieve data without modification |
| Create | Insert a new user record |
| Update | Modify existing user or profile fields |
| Delete | Soft-delete (deactivate) or hard-delete a user |
| Manage | Administrative action on another user's account (ban, activate, set password, verify email) |
Master access control matrix
Complete reference for all endpoints. Auth = authentication requirement; Authz = authorization (role/scope).
| # | Method | Path | Auth | Authz / allowed roles | Operations |
|---|---|---|---|---|---|
| 1 | POST | /api/v3/auth/login |
Public | Any caller | Execute |
| 2 | POST | /api/v3/auth/register |
Public | Any caller; creates Customer (1000) only | Create |
| 3 | POST | /api/v3/auth/token/refresh |
Public | Any caller with valid refresh token | Execute |
| 4 | POST | /api/v3/auth/token/verify |
Public | Any caller with token to verify | Execute |
| 5 | POST | /api/v3/auth/logout |
Validated JWT | Any authenticated | Execute (self) |
| 6 | POST | /api/v3/auth/password/change |
Validated JWT | Self — changes own password | Update (self) |
| 7 | POST | /api/v3/auth/password/set |
Validated JWT | Manager+ (100, 200) | Manage |
| 8 | POST | /api/v3/auth/password/reset |
Public | Any caller; target email must exist & be verified | Execute |
| 9 | POST | /api/v3/auth/password/reset/confirm |
Public | Any caller with valid reset code | Update |
| 10 | POST | /api/v3/auth/activation/send |
Public | Any caller; target email must exist | Execute |
| 11 | POST | /api/v3/auth/activation/confirm |
Public | Any caller with valid activation code | Update |
| 12 | POST | /api/v3/auth/user/activate |
Validated JWT | Manager+ (100, 200) | Manage |
| 13 | POST | /api/v3/auth/user/ban |
Validated JWT | Manager+ (100, 200) | Manage |
| 14 | POST | /api/v3/auth/user/create |
Validated JWT | Staff+ with role-specific create limits (see below) | Create |
| 15 | GET | /api/v3/users/profile/ |
Validated JWT | Self — own profile | Read (self) |
| 16 | PATCH | /api/v3/users/profile/ |
Validated JWT | Self — own profile | Update (self) |
| 17 | GET | /api/v3/users/profile/me |
Validated JWT | Self — own detailed profile | Read (self) |
| 18 | POST | /api/v3/users/profile/avatar |
Validated JWT | Self (501 not implemented) | Update (self) |
| 19 | POST | /api/v3/user/v1/users/ |
Bearer header | No role check — any Bearer string | Create |
| 20 | POST | /api/v3/user/v1/users/bulk |
Bearer header | No role check | Create |
| 21 | GET | /api/v3/user/v1/users/{user_id} |
Bearer header | No role check | Read |
| 22 | GET | /api/v3/user/v1/users/email/{email} |
Bearer header | No role check | Read |
| 23 | GET | /api/v3/user/v1/users/mobile/{mobile} |
Bearer header | No role check | Read |
| 24 | GET | /api/v3/user/v1/users/ |
Public | Any caller | Read (list) |
| 25 | PUT | /api/v3/user/v1/users/{user_id} |
Bearer header | No role check | Update |
| 26 | PATCH | /api/v3/user/v1/users/{user_id} |
Bearer header | No role check | Update |
| 27 | PUT | /api/v3/user/v1/users/bulk |
Bearer header | No role check | Update |
| 28 | DELETE | /api/v3/user/v1/users/{user_id} |
Bearer header | No role check | Delete (soft) |
| 29 | DELETE | /api/v3/user/v1/users/{user_id}/hard |
Bearer header | No role check | Delete (hard) |
| 30 | POST | /api/v3/user/v1/users/{user_id}/verify-email |
Bearer header | No role check | Manage |
| 31 | POST | /api/v3/user/v1/users/{user_id}/password |
Bearer header | No role check | Manage |
| 32 | POST | /api/v3/user/v1/users/{user_id}/log-login |
Bearer header | No role check | Execute |
| 33 | POST | /api/v3/user/v1/users/{user_id}/log-logout |
Bearer header | No role check | Execute |
| 34 | GET | /api/v3/user/v1/users/test |
Public | Any caller | Execute (health) |
Create user — role-specific authorization (`POST /api/v3/auth/user/create`)
| Caller role | Can create user_type |
Forbidden example |
|---|---|---|
| System Manager (100) | 100, 200, 300, 400, 1000, 2000, 9999 | — |
| Manager (200) | 200, 300, 400, 1000, 2000, 9999 | Creating System Manager (100) → 403 Managers cannot create system managers |
| Office Employee (300) | 1000 (Customer) only | Creating Manager (200) → 403 Office employees can only create customers |
| All other roles | — | 403 Insufficient permissions |
Authorization error responses
| HTTP | detail |
Typical cause |
|---|---|---|
| 403 | Invalid authorization code. |
Missing Authorization header (validated JWT routes) |
| 403 | Invalid authentication scheme. |
Header is not Bearer |
| 403 | Invalid token or expired token. |
Bad/expired access token (validated JWT routes) |
| 403 | Invalid token type. Access token required. |
Refresh token used where access token required |
| 403 | Only managers can set user passwords |
Non-manager calling POST /auth/password/set |
| 403 | Only managers can activate/deactivate users |
Non-manager calling POST /auth/user/activate |
| 403 | Only managers can ban/unban users |
Non-manager calling POST /auth/user/ban |
| 403 | Insufficient permissions |
Role below Staff+ calling POST /auth/user/create |
| 403 | Managers cannot create system managers |
Manager (200) creating user_type 100 |
| 403 | Office employees can only create customers |
Office Employee (300) creating non-customer |
| 403 | (OAuth2) missing credentials | User CRUD route called without Authorization header |
Access scenario examples
Authorized
# System Manager sets another user's password
curl -X POST http://127.0.0.1:8000/api/v3/auth/password/set \
-H "Authorization: Bearer <manager_access_token>" \
-H "Content-Type: application/json" \
-d '{"user": "550e8400-e29b-41d4-a716-446655440000", "password": "newpass12", "re_password": "newpass12"}'
# → 200 { "message": "Password set successfully" }# Customer reads own profile
curl http://127.0.0.1:8000/api/v3/users/profile/ \
-H "Authorization: Bearer <customer_access_token>"
# → 200 ProfileResponse (always the JWT holder's profile)# Office Employee creates a customer
curl -X POST http://127.0.0.1:8000/api/v3/auth/user/create \
-H "Authorization: Bearer <office_employee_access_token>" \
-H "Content-Type: application/json" \
-d '{"email": "new@example.com", "first_name": "Ann", "last_name": "Lee", "user_type": 1000}'
# → 201 RegisterResponse# Unauthenticated user lists active users (public)
curl "http://127.0.0.1:8000/api/v3/user/v1/users/?limit=10"
# → 200 UserListResponseUnauthorized
# Customer attempts to ban another user
curl -X POST http://127.0.0.1:8000/api/v3/auth/user/ban \
-H "Authorization: Bearer <customer_access_token>" \
-H "Content-Type: application/json" \
-d '{"user": "550e8400-e29b-41d4-a716-446655440000", "status": true}'
# → 403 { "detail": "Only managers can ban/unban users" }# Manager attempts to create a System Manager
curl -X POST http://127.0.0.1:8000/api/v3/auth/user/create \
-H "Authorization: Bearer <manager_access_token>" \
-H "Content-Type: application/json" \
-d '{"email": "admin2@example.com", "first_name": "Bob", "last_name": "Admin", "user_type": 100}'
# → 403 { "detail": "Managers cannot create system managers" }# Request profile without token
curl http://127.0.0.1:8000/api/v3/users/profile/
# → 403 { "detail": "Invalid authorization code." }# Use refresh token instead of access token on a validated-JWT route
curl http://127.0.0.1:8000/api/v3/users/profile/ \
-H "Authorization: Bearer <refresh_token>"
# → 403 { "detail": "Invalid token type. Access token required." }# User CRUD route without Authorization header
curl http://127.0.0.1:8000/api/v3/user/v1/users/550e8400-e29b-41d4-a716-446655440000
# → 403 (OAuth2 — not authenticated)Authentication flow
Common errors
HTTP status codes
| Code | Meaning |
|---|---|
200 |
Success |
201 |
Created |
204 |
Success, no body |
400 |
Validation or business rule failure |
401 |
Invalid credentials or token |
403 |
Forbidden (role or bearer scheme) |
404 |
Resource not found |
422 |
Pydantic validation error (request body) |
501 |
Not implemented |
503 |
Database unavailable |
Bearer middleware (`403`)
detail |
Cause |
|---|---|
Invalid authorization code. |
Missing Authorization header |
Invalid authentication scheme. |
Not Bearer |
Invalid token or expired token. |
Bad or expired JWT |
Invalid token type. Access token required. |
Refresh token used as access |
Authorization failures (`403`)
Role or permission denied on protected endpoints. See Authorization error responses for the full list.
detail |
Endpoint(s) | Who is denied |
|---|---|---|
Only managers can set user passwords |
POST /auth/password/set |
All roles except 100, 200 |
Only managers can activate/deactivate users |
POST /auth/user/activate |
All roles except 100, 200 |
Only managers can ban/unban users |
POST /auth/user/ban |
All roles except 100, 200 |
Insufficient permissions |
POST /auth/user/create |
400, 1000, 2000, 9999 |
Managers cannot create system managers |
POST /auth/user/create |
Manager (200) creating user_type 100 |
Office employees can only create customers |
POST /auth/user/create |
Office Employee (300) creating non-1000 |
Login failures (`401`)
detail |
Cause |
|---|---|
Invalid credentials |
Wrong email/password or user not found |
User account is inactive |
is_active=false |
User account is banned |
is_banned=true |
Email not verified |
email_verified=false |
Pydantic validation errors (`422`)
When request body or query parameters fail schema validation, FastAPI returns 422 Unprocessable Entity:
{
"detail": [
{
"type": "string_too_short",
"loc": ["body", "password"],
"msg": "String should have at least 8 characters",
"input": "short",
"ctx": { "min_length": 8 }
}
]
}Each item in detail includes loc (field path), msg, type, and often ctx with min_length, max_length, or other bounds.
Field validation & constraints
Constraints come from Pydantic request schemas (src/auth/schemas.py, src/user/schemas/user.py), database columns (src/user/models/user.py), and service-layer rules (src/auth/service.py).
Where a Pydantic schema does not set a bound, the database column limit still applies at persistence time.
Notation
| Symbol | Meaning |
|---|---|
| — | No min/max enforced at the documented layer |
| (DB) | Enforced by PostgreSQL column definition |
| (service) | Enforced in service layer after schema validation |
| (computed) | Derived in response; not accepted in requests |
Validation layers (in order):
- Pydantic / FastAPI
Query→ 422 on failure (body or query params) - Service / business logic → 400 or 401 with
detailmessage - Database → 400 on unique violations; truncation or error on column overflow
String fields (request & path)
| Field | Endpoints | Required | Min length | Max length | Format / other constraints |
|---|---|---|---|---|---|
email |
Login, register, reset, activation, manager create, user CRUD | Varies | 1 (EmailStr) | 320 (EmailStr) | Valid email (EmailStr / RFC 5321). Must be unique in core_user. Login lookup is case-insensitive (normalized to lowercase). User CRUD uses plain str with no Pydantic max (DB: unlimited TEXT). |
password |
Login, register, change/set/reset password, user CRUD create | Varies | 8 (auth) / 1 (login, service) | — | Plain text in request; stored as PBKDF2-SHA256 hash (pbkdf2_sha256$…). Login schema defaults to ""; service rejects empty/falsy password (401). User CRUD has no min/max in schema. No API max length. |
re_password |
Register, change/set/reset password | Where password is sent |
8 | — | Must exactly match password (Pydantic validator). |
current_password |
Change password | Yes | — | — | No length bound in schema; verified against stored hash. |
first_name |
Register, manager create, profile update, user CRUD | Varies | 1 | 150 | Non-empty when required. DB column max_length=150. |
last_name |
Register, manager create, profile update, user CRUD | Varies | 1 | 150 | Non-empty when required. DB column max_length=150. |
mobile |
Register, manager create, user CRUD | No | — | 11 | Must be unique if provided. DB column max_length=11. |
refresh |
Token refresh, logout | Yes | — | — | JWT refresh token string. No Pydantic min_length; empty string passes schema but fails JWT decode (401). Accepts alias refresh_token in request body. |
token (JWT) |
Token verify | Yes | — | — | JWT string (access or refresh). No Pydantic length bound; invalid/empty fails decode (401). |
avatar |
User CRUD update (response fields) | No | — | — | Optional URL/path string; no API-level max. Upload endpoint not implemented. |
new_password |
POST …/users/{id}/password |
Yes (query) | — | — | Passed as query parameter ?new_password=…; no schema validation in router. Stored as provided (should be pre-hashed by caller — see implementation note). |
Path: email |
GET …/users/email/{email} |
— | — | — | URL-encoded email string; exact match against stored value (case-sensitive in CRUD). |
Path: mobile |
GET …/users/mobile/{mobile} |
— | 1 | 11 | Exact match; max 11 chars per DB. |
String fields (response only)
| Field | Min length | Max length | Format / notes |
|---|---|---|---|
access |
1 | — | JWT access token (~200–600 chars typical) |
refresh |
1 | — | JWT refresh token |
message |
0 | — | Human-readable status string |
detail |
1 | — | Error or success detail (e.g. token verify) |
full_name |
1 | 301 | (computed) first_name (≤150) + " " + last_name (≤150) |
joined_at, login_at, logout_at, created_at, updated_at |
19 | 19 | Datetime string when non-null: YYYY.MM.DD HH:MM:SS |
UUID fields in JSON (id, etc.) |
36 | 36 | Standard hyphenated UUID string |
Numeric fields
| Field | Endpoints | Type | Min | Max | Other constraints |
|---|---|---|---|---|---|
token (verification code) |
Activation confirm, password reset confirm | integer | 100000 | 999999 | Exactly 6 digits. Generated via random.randint(100000, 999999). Valid for 900 seconds (15 min, EMAIL_TOKEN_EXPIRE). Values outside range pass Pydantic but fail service validation (400). |
code (debug response) |
Activation/reset send | integer | 100000 | 999999 | Returned only when debug=true. |
user_type |
Login response, manager create, profile response | integer | 100 | 9999 | Discrete allowed values only: 100, 200, 300, 400, 1000, 2000, 9999 — not a continuous range. Stored as PostgreSQL SmallInteger (-32768–32767). Invalid values may persist on User CRUD paths without enum check. |
total |
List users response | integer | 0 | — | Count of active users (is_active=true) regardless of active_only filter. |
exp_time |
Login response | integer | 300 | 86400 | Access-token TTL in seconds: 300 (5 min) in production; 86400 (24 h) when debug=true. |
timeout |
Activation/reset send response | integer | 900 | 900 | Token validity period in seconds (EMAIL_TOKEN_EXPIRE). |
offset |
List users | integer | 0 | — | ge=0. Number of records to skip. |
limit |
List users | integer | 1 | 1000 | ge=1, le=1000. Page size. |
_version |
(internal, response via DB) | integer | 1 | — | Optimistic versioning; incremented on each update. |
Array fields
| Field | Endpoints | Min items | Max items | Item constraints |
|---|---|---|---|---|
users |
Bulk create, bulk update | 1 | — | Each element follows UserCreate / bulk-update item rules |
Boolean fields
| Field | Endpoints | Default | Constraints |
|---|---|---|---|
status |
Activate/deactivate, ban/unban user | — | Required. true = activate/ban; false = deactivate/unban. |
is_active |
Register (implicit), manager create, user CRUD, profile /me |
true |
DB boolean. Registration sets is_active=true but email_verified=false until activation. |
two_step_auth |
Profile get/update | false |
Optional on PATCH; toggles 2FA flag (implementation TBD). |
notify_after_login |
Profile get/update | false |
Optional on PATCH. |
active_only |
List users (query) | true |
When true, returns only is_active=true users. |
UUID fields
| Field | Endpoints | Format | Constraints |
|---|---|---|---|
id |
Responses, path {user_id} |
UUID v4 | RFC 4122. Path parameter must be valid UUID or 422. |
user |
Activate, ban, set password | UUID | Target user ID; must exist or 400 / 404. |
Password & token rules (service layer)
| Rule | Detail |
|---|---|
| Password hashing | PBKDF2-SHA256, 600,000 iterations, Django-compatible format. |
| Password min length | 8 characters on all password-setting endpoints (Pydantic). |
validate_password_strength() |
Exists in src/auth/security.py (rejects all-numeric and common passwords) but is not called by register/change endpoints today — only Pydantic min_length=8 applies. |
| Verification code range | 100000–999999 (inclusive). |
| Verification code TTL | 900 s (15 minutes). |
| Manager-generated password | Random 8 characters (ascii_letters + digits) on POST /auth/user/create. |
| JWT access lifetime | 300 s prod / 86400 s debug. |
| JWT refresh lifetime | 86400 s (24 h). |
Uniqueness constraints (database)
| Column | Constraint |
|---|---|
email |
Unique index; duplicate returns 400 (Email already registered). |
mobile |
Unique index (when not null); duplicate returns 400 (Mobile number already registered). |
Fields with no API-level length validation
These fields have no Pydantic min/max on the User CRUD router but are bounded by the database or business logic:
| Field | DB / behavior limit |
|---|---|
email (User CRUD) |
Optional[str], unique; no max_length on column — recommend ≤ 320 |
password (User CRUD create) |
Required string; no min/max in schema; not auto-hashed |
first_name, last_name (User CRUD) |
DB max_length=150; no Pydantic min on create |
avatar (User CRUD update) |
No column max defined |
new_password (query param) |
No validation; any length accepted |
Query, filtering, sorting & pagination
Summary by endpoint group
| Capability | Auth (/api/v3/auth) |
Profile (/api/v3/users/profile) |
User CRUD (/api/v3/user/v1/users) |
|---|---|---|---|
| Pagination | Not supported | Not supported | List users only (offset, limit) |
| Filtering | Not supported | Not supported | List users only (active_only) |
| Sorting | Not supported | Not supported | Not supported (order clause commented out in CRUD) |
| Full-text search | Not supported | Not supported | Not supported |
| Field-specific query filters | Not supported | Not supported | Not supported (no email, user_type, etc. query params) |
Per-endpoint query & list capabilities
Every endpoint in this document and whether it accepts query parameters, path filters, sorting, or search.
| # | Method | Path | Query params | Sort | Filter | Search | Pagination |
|---|---|---|---|---|---|---|---|
| 1 | POST | /api/v3/auth/login |
— | — | — | — | — |
| 2 | POST | /api/v3/auth/register |
— | — | — | — | — |
| 3 | POST | /api/v3/auth/token/refresh |
— | — | — | — | — |
| 4 | POST | /api/v3/auth/token/verify |
— | — | — | — | — |
| 5 | POST | /api/v3/auth/logout |
— | — | — | — | — |
| 6 | POST | /api/v3/auth/password/change |
— | — | — | — | — |
| 7 | POST | /api/v3/auth/password/set |
— | — | — | — | — |
| 8 | POST | /api/v3/auth/password/reset |
— | — | — | — | — |
| 9 | POST | /api/v3/auth/password/reset/confirm |
— | — | — | — | — |
| 10 | POST | /api/v3/auth/activation/send |
— | — | — | — | — |
| 11 | POST | /api/v3/auth/activation/confirm |
— | — | — | — | — |
| 12 | POST | /api/v3/auth/user/activate |
— | — | — | — | — |
| 13 | POST | /api/v3/auth/user/ban |
— | — | — | — | — |
| 14 | POST | /api/v3/auth/user/create |
— | — | — | — | — |
| 15 | GET | /api/v3/users/profile/ |
— | — | — | — | — |
| 16 | PATCH | /api/v3/users/profile/ |
— | — | — | — | — |
| 17 | GET | /api/v3/users/profile/me |
— | — | — | — | — |
| 18 | POST | /api/v3/users/profile/avatar |
— | — | — | — | — |
| 19 | POST | /api/v3/user/v1/users/ |
— | — | — | — | — |
| 20 | POST | /api/v3/user/v1/users/bulk |
— | — | — | — | — |
| 21 | GET | /api/v3/user/v1/users/{user_id} |
— | — | Path: UUID lookup | — | — |
| 22 | GET | /api/v3/user/v1/users/email/{email} |
— | — | Path: exact email | — | — |
| 23 | GET | /api/v3/user/v1/users/mobile/{mobile} |
— | — | Path: exact mobile | — | — |
| 24 | GET | /api/v3/user/v1/users/ |
offset, limit, active_only |
— | active_only |
— | offset/limit |
| 25 | PUT | /api/v3/user/v1/users/{user_id} |
— | — | — | — | — |
| 26 | PATCH | /api/v3/user/v1/users/{user_id} |
— | — | — | — | — |
| 27 | PUT | /api/v3/user/v1/users/bulk |
— | — | — | — | — |
| 28 | DELETE | /api/v3/user/v1/users/{user_id} |
— | — | — | — | — |
| 29 | DELETE | /api/v3/user/v1/users/{user_id}/hard |
— | — | — | — | — |
| 30 | POST | /api/v3/user/v1/users/{user_id}/verify-email |
— | — | — | — | — |
| 31 | POST | /api/v3/user/v1/users/{user_id}/password |
new_password |
— | — | — | — |
| 32 | POST | /api/v3/user/v1/users/{user_id}/log-login |
— | — | — | — | — |
| 33 | POST | /api/v3/user/v1/users/{user_id}/log-logout |
— | — | — | — | — |
| 34 | GET | /api/v3/user/v1/users/test |
— | — | — | — | — |
Lookup vs list: Endpoints 21–23 perform exact-match retrieval by path segment (not query-string filters). Endpoint 24 is the only paginated collection endpoint.
List users — `GET /api/v3/user/v1/users/`
The only endpoint in this section that supports query parameters.
| Parameter | Type | Required | Default | Min | Max | Description |
|---|---|---|---|---|---|---|
offset |
integer | No | 0 |
0 | — | Records to skip (offset-based pagination). |
limit |
integer | No | 100 |
1 | 1000 | Maximum records per page. |
active_only |
boolean | No | true |
— | — | true → only is_active=true; false → all users from view. |
Pagination response fields
| Field | Type | Description |
|---|---|---|
users |
array | Current page of UserResponse objects. |
total |
integer | Count of active users only (crud.count() filters is_active=true regardless of active_only). |
offset |
integer | Echo of request offset. |
limit |
integer | Echo of request limit. |
Pagination example
# Page 1 — first 50 active users
curl "http://127.0.0.1:8000/api/v3/user/v1/users/?offset=0&limit=50&active_only=true"
# Page 2
curl "http://127.0.0.1:8000/api/v3/user/v1/users/?offset=50&limit=50&active_only=true"
# Include inactive users (still no sort order guaranteed)
curl "http://127.0.0.1:8000/api/v3/user/v1/users/?offset=0&limit=100&active_only=false"Sorting: Not supported. list_active() and list_all() have commented-out .order_by(created_at.desc()) — result order is undefined (database-dependent).
Search: Not supported. No q, search, query, or full-text parameters.
Filter: Only active_only (boolean). No filters for user_type, is_banned, email_verified, email, mobile, name, or date ranges.
Pagination type: Offset-based (not cursor-based). There is no page parameter — compute offset = page_index × limit.
Rejected query values: offset < 0, limit < 1, or limit > 1000 → 422 with Pydantic validation error.
Search / filter alternatives: No query parameters for email, name, user_type, is_banned, date ranges, or free-text search. Use dedicated lookup endpoints instead:
| Lookup | Endpoint |
|---|---|
| By ID | GET /api/v3/user/v1/users/{user_id} |
| By email | GET /api/v3/user/v1/users/email/{email} |
| By mobile | GET /api/v3/user/v1/users/mobile/{mobile} |
Update password query parameter
POST /api/v3/user/v1/users/{user_id}/password
| Parameter | Type | Required | Location | Constraints |
|---|---|---|---|---|
new_password |
string | Yes | Query string | No min/max validation in router |
Path-parameter lookups (no query string)
| Endpoint | Path param | Constraints |
|---|---|---|
GET …/users/{user_id} |
user_id |
Valid UUID |
GET …/users/email/{email} |
email |
Full email as path segment; URL-encode special characters |
GET …/users/mobile/{mobile} |
mobile |
Max 11 characters |
Endpoints with no query/path parameters beyond auth
All /api/v3/auth endpoints (except path segments shown in URL) and all /api/v3/users/profile endpoints accept only JSON body fields or no parameters. No pagination, filtering, or sorting applies.
Authentication APIs (`/api/v3/auth`)
1. Login
| Name | User login |
| URL | POST /api/v3/auth/login |
| Authentication | None (public) |
| Authorization | Any caller |
| Allowed roles | — (unauthenticated) |
| Operations | Execute — obtain JWT tokens |
Request body (JSON)
| Field | Type | Required | Min length | Max length | Min value | Max value | Description |
|---|---|---|---|---|---|---|---|
email |
string (email) | Yes | 1 | 320 | — | — | Valid email format; lookup is case-insensitive |
password |
string | Yes | 1 (service) | — | — | — | Plain-text password; empty string rejected (401) |
Response 200 — TokenResponse
| Field | Type | Required | Min length | Max length | Min value | Max value | Description |
|---|---|---|---|---|---|---|---|
access |
string | — | 1 | — | — | — | JWT access token |
refresh |
string | — | 1 | — | — | — | JWT refresh token |
exp_time |
integer | — | — | — | 300 | 86400 | Access TTL in seconds (300 prod, 86400 debug) |
user_type |
integer | — | — | — | 100 | 9999 | Role code; allowed values: 100, 200, 300, 400, 1000, 2000, 9999 |
Errors: 401 (login failures), 503 (database connection failed)
Example
curl -X POST http://127.0.0.1:8000/api/v3/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "admin@test.com", "password": "admin123"}'{
"access": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...",
"exp_time": 300,
"user_type": 100
}2. Register
| Name | User registration |
| URL | POST /api/v3/auth/register |
| Authentication | None (public) |
| Authorization | Any caller; always creates Customer (1000) |
| Allowed roles | — (unauthenticated) |
| Operations | Create |
Request body (JSON)
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
email |
string (email) | Yes | 1 | 320 | — | — | Unique; valid email format |
first_name |
string | Yes | 1 | 150 | — | — | |
last_name |
string | Yes | 1 | 150 | — | — | |
password |
string | Yes | 8 | — | — | — | |
re_password |
string | Yes | 8 | — | — | — | Must equal password |
mobile |
string | No | — | 11 | — | — | Unique if set; omit or null |
New users are created as Customer (user_type=1000).
Response 201 — RegisterResponse
| Field | Type | Min length | Max length | Min value | Max value |
|---|---|---|---|---|---|
id |
UUID | 36 | 36 | — | — |
email |
string | 1 | 320 | — | — |
first_name |
string | 1 | 150 | — | — |
last_name |
string | 1 | 150 | — | — |
message |
string | 1 | — | — | — |
Errors: 400 (Email already registered, Mobile number already registered), 422 (Passwords do not match)
3. Refresh token
| URL | POST /api/v3/auth/token/refresh |
| Authentication | None (public) |
| Authorization | Any caller with a valid, non-expired refresh token |
| Allowed roles | — (unauthenticated; role embedded in refresh token is not checked) |
| Operations | Execute |
Request body
| Field | Type | Required | Min length | Max length | Aliases | Other |
|---|---|---|---|---|---|---|
refresh |
string | Yes | — | — | refresh_token |
Valid JWT refresh token; empty/invalid → 401 |
Response 200
| Field | Type | Min length | Max length |
|---|---|---|---|
access |
string | 1 | — |
{ "access": "<new_jwt>" }Errors: 401 — Invalid or expired refresh token
4. Verify token
| URL | POST /api/v3/auth/token/verify |
| Authentication | None (public) |
| Authorization | Any caller with a token to verify |
| Allowed roles | — |
| Operations | Execute |
Request body
| Field | Type | Required | Min length | Max length | Other |
|---|---|---|---|---|---|
token |
string | Yes | — | — | JWT access or refresh; invalid → 401 |
Response 200: { "detail": "Token is valid" }
Errors: 401 — Invalid or expired token
5. Logout
| URL | POST /api/v3/auth/logout |
| Authentication | Validated JWT access token |
| Authorization | Any authenticated user |
| Allowed roles | 100, 200, 300, 400, 1000, 2000, 9999 |
| Operations | Execute (self) |
Request body
| Field | Type | Required | Min length | Max length | Other |
|---|---|---|---|---|---|
refresh |
string | Yes | — | — | Valid JWT refresh token |
Response 200: { "message": "Successfully logged out" }
Side effect: Marks user logout timestamp; invalidates refresh token server-side.
6. Change password
| URL | POST /api/v3/auth/password/change |
| Authentication | Validated JWT access token |
| Authorization | Self — changes password for JWT holder only |
| Allowed roles | 100, 200, 300, 400, 1000, 2000, 9999 |
| Operations | Update (self) |
Request body
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
current_password |
string | Yes | — | — | — | — | Verified against stored hash |
password |
string | Yes | 8 | — | — | — | New password |
re_password |
string | Yes | 8 | — | — | — | Must equal password |
Response 200: { "message": "Password changed successfully" }
Errors: 400 (User not found, Current password is incorrect), 422 (length / match validation)
7. Set password (manager)
| URL | POST /api/v3/auth/password/set |
| Authentication | Validated JWT access token |
| Authorization | Manager+ — set password for another user |
| Allowed roles | System Manager (100), Manager (200) |
| Operations | Manage |
Request body
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
user |
UUID | Yes | 36 | 36 | — | — | Target user ID; must exist |
password |
string | Yes | 8 | — | — | — | |
re_password |
string | Yes | 8 | — | — | — | Must equal password |
Response 200: { "message": "Password set successfully" }
Errors: 403 — Only managers can set user passwords (Customer, Office Employee, Warehouse Employee, etc.); 422 — validation
Unauthorized example: Customer (1000) with valid token → 403
8. Password reset — send code
| URL | POST /api/v3/auth/password/reset |
| Authentication | None (public) |
| Authorization | Any caller; email must belong to registered, verified, non-banned user |
| Allowed roles | — |
| Operations | Execute |
Request body
| Field | Type | Required | Min length | Max length | Other |
|---|---|---|---|---|---|
email |
string (email) | Yes | 1 | 320 | Registered, verified, non-banned user |
Response 200 — SendActivationResponse
| Field | Type | Min | Max | Notes |
|---|---|---|---|---|
timeout |
integer | 900 | 900 | Code validity in seconds (EMAIL_TOKEN_EXPIRE) |
code |
integer | 100000 | 999999 | Only when debug=true |
Errors: 400 (Email not registered, User is banned, Email not verified)
9. Password reset — confirm
| URL | POST /api/v3/auth/password/reset/confirm |
| Authentication | None (public) |
| Authorization | Any caller with valid reset code for the given email |
| Allowed roles | — |
| Operations | Update |
Request body
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
email |
string (email) | Yes | 1 | 320 | — | — | |
token |
integer | Yes | — | — | 100000 | 999999 | 6-digit code; expires after 900 s |
password |
string | Yes | 8 | — | — | — | |
re_password |
string | Yes | 8 | — | — | — | Must equal password |
Response 200: { "message": "Password reset successfully" }
Errors: 400 (Invalid token, Token has expired, …), 422 (validation)
10. Activation — send code
| URL | POST /api/v3/auth/activation/send |
| Authentication | None (public) |
| Authorization | Any caller; email must belong to existing, non-banned, unverified user |
| Allowed roles | — |
| Operations | Execute |
Request body
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
email |
string (email) | Yes | 1 | 320 | — | — | User must exist, not banned, not already verified |
Response 200 — SendActivationResponse
| Field | Type | Min | Max |
|---|---|---|---|
timeout |
integer | 900 | 900 |
code |
integer | 100000 | 999999 (debug only) |
Errors: 400 (Email already verified, Email not registered, User is banned)
11. Activation — confirm
| URL | POST /api/v3/auth/activation/confirm |
| Authentication | None (public) |
| Authorization | Any caller with valid activation code for the given email |
| Allowed roles | — |
| Operations | Update |
Request body
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
email |
string (email) | Yes | 1 | 320 | — | — | |
token |
integer | Yes | — | — | 100000 | 999999 | Expires after 900 s |
Response 200: { "message": "Email activated successfully" }
Side effect: Sets email_verified=true, is_active=true, clears verification token.
12. Activate / deactivate user
| URL | POST /api/v3/auth/user/activate |
| Authentication | Validated JWT access token |
| Authorization | Manager+ — activate/deactivate another user |
| Allowed roles | System Manager (100), Manager (200) |
| Operations | Manage |
Request body
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
user |
UUID | Yes | 36 | 36 | — | — | Target user ID |
status |
boolean | Yes | — | — | — | — | true = activate, false = deactivate |
Response 200: { "message": "User activated" } or "User deactivated"
Errors: 403 — Only managers can activate/deactivate users
Unauthorized example: Warehouse Employee (400) → 403
13. Ban / unban user
| URL | POST /api/v3/auth/user/ban |
| Authentication | Validated JWT access token |
| Authorization | Manager+ — ban/unban another user |
| Allowed roles | System Manager (100), Manager (200) |
| Operations | Manage |
Request body
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
user |
UUID | Yes | 36 | 36 | — | — | Target user ID |
status |
boolean | Yes | — | — | — | — | true = ban, false = unban |
Response 200: { "message": "User banned" } or "User unbanned"
14. Create user (manager)
| URL | POST /api/v3/auth/user/create |
| Authentication | Validated JWT access token |
| Authorization | Staff+ with role-specific limits (see matrix) |
| Allowed roles | System Manager (100), Manager (200), Office Employee (300) |
| Operations | Create |
Request body
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
email |
string (email) | Yes | 1 | 320 | — | — | Unique |
first_name |
string | Yes | 1 | 150 | — | — | |
last_name |
string | Yes | 1 | 150 | — | — | |
user_type |
integer | Yes | — | — | 100 | 9999 | Allowed: 100, 200, 300, 400, 1000, 2000, 9999; role restrictions apply |
is_active |
boolean | No | — | — | — | — | Default true |
mobile |
string | No | — | 11 | — | — | Unique if set |
Response 201 — RegisterResponse
| Field | Type | Notes |
|---|---|---|
id |
UUID | |
email |
string | |
first_name |
string | Max 150 |
last_name |
string | Max 150 |
message |
string | Includes auto-generated 8-character password |
Response 201: message includes generated password (send via email in production).
Errors: 403 (Insufficient permissions, role-specific restrictions), 400 (Email already registered)
Profile APIs (`/api/v3/users/profile`)
All routes require a validated JWT access token (JWTBearer applied at router level).
Authorization model: Self only — every profile endpoint reads or updates the user identified by the JWT user_id claim. No role may access another user's profile through these routes.
| Role | Read own profile | Update own profile | Upload avatar |
|---|---|---|---|
| System Manager (100) | ✓ | ✓ | ✓ (501) |
| Manager (200) | ✓ | ✓ | ✓ (501) |
| Office Employee (300) | ✓ | ✓ | ✓ (501) |
| Warehouse Employee (400) | ✓ | ✓ | ✓ (501) |
| Customer (1000) | ✓ | ✓ | ✓ (501) |
| Customs Officer (2000) | ✓ | ✓ | ✓ (501) |
| Undefined (9999) | ✓ | ✓ | ✓ (501) |
15. Get profile
| URL | GET /api/v3/users/profile/ |
| Authentication | Validated JWT access token |
| Authorization | Self |
| Allowed roles | Any authenticated (100–9999) |
| Operations | Read (self) |
Query parameters: None.
Response 200 — ProfileResponse
| Field | Type | Min length | Max length | Min value | Max value | Notes |
|---|---|---|---|---|---|---|
id |
UUID | 36 | 36 | — | — | |
full_name |
string | 1 | 301 | — | — | (computed) |
first_name |
string | 1 | 150 | — | — | |
last_name |
string | 1 | 150 | — | — | |
email |
string | 1 | 320 | — | — | |
user_type |
integer | — | — | 100 | 9999 | Enum value |
avatar |
string | null | — | — | — | — | URL/path or null |
two_step_auth |
boolean | — | — | — | — | Default false |
notify_after_login |
boolean | — | — | — | — | Default false |
16. Update profile
| URL | PATCH /api/v3/users/profile/ |
| Authentication | Validated JWT access token |
| Authorization | Self |
| Allowed roles | Any authenticated (100–9999) |
| Operations | Update (self) |
Query parameters: None.
Request body (all fields optional — send only fields to change)
| Field | Type | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|
first_name |
string | 1 | 150 | — | — | Omit to leave unchanged |
last_name |
string | 1 | 150 | — | — | Omit to leave unchanged |
two_step_auth |
boolean | — | — | — | — | |
notify_after_login |
boolean | — | — | — | — |
Response 200: ProfileResponse (same shape as GET profile)
17. Get detailed profile (`/me`)
| URL | GET /api/v3/users/profile/me |
| Authentication | Validated JWT access token |
| Authorization | Self |
| Allowed roles | Any authenticated (100–9999) |
| Operations | Read (self) |
Query parameters: None.
Response 200 — UserDetailResponse
| Field | Type | Min length | Max length | Min value | Max value | Notes |
|---|---|---|---|---|---|---|
id |
UUID | 36 | 36 | — | — | |
full_name |
string | 1 | 301 | — | — | (computed) |
first_name |
string | 1 | 150 | — | — | |
last_name |
string | 1 | 150 | — | — | |
email |
string | 1 | 320 | — | — | |
email_verified |
boolean | — | — | — | — | |
user_type |
integer | — | — | 100 | 9999 | |
is_active |
boolean | — | — | — | — | |
can_delete |
boolean | — | — | — | — | Maps to deletable |
mobile |
string | null | — | 11 | — | — | |
mobile_verified |
boolean | — | — | — | — | |
is_staff |
boolean | — | — | — | — | |
joined_at |
string | null | 19 | 19 | — | — | YYYY.MM.DD HH:MM:SS or null |
logout_at |
string | null | 19 | 19 | — | — | |
login_at |
string | null | 19 | 19 | — | — | |
is_banned |
boolean | — | — | — | — | |
notify_after_login |
boolean | — | — | — | — | |
two_step_auth |
boolean | — | — | — | — | |
avatar |
string | null | — | — | — | — | |
created_at |
string | null | 19 | 19 | — | — | |
updated_at |
string | null | 19 | 19 | — | — |
18. Upload avatar
| URL | POST /api/v3/users/profile/avatar |
| Authentication | Validated JWT access token |
| Authorization | Self |
| Allowed roles | Any authenticated (100–9999) |
| Operations | Update (self) |
| Status | 501 Not Implemented — { "detail": "Avatar upload not yet implemented" } |
User CRUD APIs (`/api/v3/user/v1/users`)
Low-level user table operations.
Authentication note: Protected routes use
OAuth2PasswordBearer, which requires anAuthorization: Bearer …header but does not validate the JWT signature, expiry, or role. Any non-empty Bearer token satisfies the dependency.Authorization note: No role-based access control is implemented on these routes. Any caller with a Bearer header (or no header for public routes) can perform CRUD operations.
User CRUD access summary
| # | Endpoint | Authentication | Authorization | Allowed roles |
|---|---|---|---|---|
| 19 | POST / |
Bearer header | None | Any (token not validated) |
| 20 | POST /bulk |
Bearer header | None | Any |
| 21 | GET /{user_id} |
Bearer header | None | Any |
| 22 | GET /email/{email} |
Bearer header | None | Any |
| 23 | GET /mobile/{mobile} |
Bearer header | None | Any |
| 24 | GET / |
Public | None | Any caller |
| 25–26 | PUT/PATCH /{user_id} |
Bearer header | None | Any |
| 27 | PUT /bulk |
Bearer header | None | Any |
| 28 | DELETE /{user_id} |
Bearer header | None | Any |
| 29 | DELETE /{user_id}/hard |
Bearer header | None | Any |
| 30 | POST /{user_id}/verify-email |
Bearer header | None | Any |
| 31 | POST /{user_id}/password |
Bearer header | None | Any |
| 32–33 | POST /{user_id}/log-login|log-logout |
Bearer header | None | Any |
| 34 | GET /test |
Public | None | Any caller |
For production use, prefer the /api/v3/auth manager endpoints which enforce validated JWT and role checks.
19. Create user
POST /api/v3/user/v1/users/ — 201 UserResponse
| Authentication | Bearer header (not validated) | | Authorization | None — no role check | | Allowed roles | Any caller presenting a Bearer token | | Operations | Create |
Request body
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
first_name |
string | Yes | — | 150 (DB) | — | — | No Pydantic min; empty may fail at DB |
last_name |
string | Yes | — | 150 (DB) | — | — | |
password |
string | Yes | — | — | — | — | Stored as provided (not auto-hashed by CRUD) |
email |
string | No | — | — (DB: unlimited) | — | — | Unique if set; no EmailStr validation |
mobile |
string | No | — | 11 (DB) | — | — | Unique if set |
is_active |
boolean | No | — | — | — | — | Default true |
Response 201 — UserResponse
| Field | Type | Min length | Max length | Min value | Max value |
|---|---|---|---|---|---|
id |
UUID | 36 | 36 | — | — |
email |
string | null | — | — | — | — |
first_name |
string | — | 150 (DB) | — | — |
last_name |
string | — | 150 (DB) | — | — |
is_active |
boolean | — | — | — | — |
mobile |
string | null | — | 11 | — | — |
avatar |
string | null | — | — | — | — |
created_at |
string | null | 19 | 19 | — | — |
updated_at |
string | null | 19 | 19 | — | — |
20. Bulk create
POST /api/v3/user/v1/users/bulk — 201 UserResponse[]
| Authentication | Bearer header (not validated) | | Authorization | None | | Operations | Create |
Request body
| Field | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
users |
array of UserCreate |
Yes | 1 (items) | — | — | — | No max array size in schema; each item follows create-user rules |
Query parameters: None. No pagination, sorting, or filtering.
21. Get user by ID
GET /api/v3/user/v1/users/{user_id} — 200 UserResponse | 404
| Authentication | Bearer header (not validated) | | Authorization | None — any role can read any user by ID | | Operations | Read |
Path parameters
| Param | Type | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|
user_id |
UUID | 36 | 36 | — | — | Valid RFC 4122 UUID; invalid → 422 |
Query parameters: None. No sorting/filtering.
22. Get user by email
GET /api/v3/user/v1/users/email/{email} — 200 | 404
| Authentication | Bearer header (not validated) | | Authorization | None | | Operations | Read |
Path parameters
| Param | Type | Min length | Max length | Other |
|---|---|---|---|---|
email |
string | 1 | — (DB: unlimited) | Exact match; URL-encode @ as %40 |
Query parameters: None.
23. Get user by mobile
GET /api/v3/user/v1/users/mobile/{mobile} — 200 | 404
| Authentication | Bearer header (not validated) | | Authorization | None | | Operations | Read |
Path parameters
| Param | Type | Min length | Max length | Other |
|---|---|---|---|---|
mobile |
string | 1 | 11 | Exact match |
Query parameters: None.
24. List users
GET /api/v3/user/v1/users/ — 200 UserListResponse
| Authentication | None (public) | | Authorization | None — unauthenticated access allowed | | Allowed roles | Any caller | | Operations | Read (list) |
See Query, filtering, sorting & pagination for full details.
Query parameters
| Param | Type | Required | Default | Min | Max | Description |
|---|---|---|---|---|---|---|
offset |
integer | No | 0 |
0 | — | Pagination offset |
limit |
integer | No | 100 |
1 | 1000 | Page size |
active_only |
boolean | No | true |
— | — | Filter by is_active |
Not supported: sort, order, search, q, user_type, email, or any other filter query params.
Auth note: Token check is commented out in source — fully public.
Unauthorized example: N/A — no authentication required.
Response 200 — UserListResponse
| Field | Type | Description |
|---|---|---|
users |
UserResponse[] |
Current page; each item follows UserResponse field limits |
total |
integer | Count of active users only; min 0 |
offset |
integer | Echo of request; min 0 |
limit |
integer | Echo of request; min 1, max 1000 |
{
"users": [ { "id": "...", "first_name": "...", ... } ],
"total": 42,
"offset": 0,
"limit": 100
}25. Update user (full)
PUT /api/v3/user/v1/users/{user_id} — 200 UserResponse
| Authentication | Bearer header (not validated) | | Authorization | None — any role can update any user | | Operations | Update |
Path: user_id — UUID
Request body (UserUpdate — all fields optional)
| Field | Type | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|
email |
string | — | — (DB: unlimited) | — | — | |
first_name |
string | — | 150 (DB) | — | — | |
last_name |
string | — | 150 (DB) | — | — | |
mobile |
string | — | 11 (DB) | — | — | |
is_active |
boolean | — | — | — | — | |
avatar |
string | — | — | — | — |
Protected fields (cannot update via CRUD): id, _created_at, _created_by
26. Partial update
PATCH /api/v3/user/v1/users/{user_id} — same body and constraints as PUT; only sent fields are updated.
| Authentication | Bearer header (not validated) | | Authorization | None | | Operations | Update |
27. Bulk update
PUT /api/v3/user/v1/users/bulk — 200 UserResponse[]
| Authentication | Bearer header (not validated) | | Authorization | None | | Operations | Update |
Request body
| Field | Type | Required | Min length | Max length | Other |
|---|---|---|---|---|---|
users |
array | Yes | 1 (items) | — | No max array size; implementation expects user_id per item |
Query parameters: None.
28. Soft delete
DELETE /api/v3/user/v1/users/{user_id} — 204 (sets is_active=false)
| Authentication | Bearer header (not validated) | | Authorization | None | | Operations | Delete (soft) |
Path: user_id — UUID. No query parameters.
29. Hard delete
DELETE /api/v3/user/v1/users/{user_id}/hard — 204 (permanent row deletion)
| Authentication | Bearer header (not validated) | | Authorization | None | | Operations | Delete (hard) |
Path: user_id — UUID. No query parameters.
30. Verify email (admin)
POST /api/v3/user/v1/users/{user_id}/verify-email — 200 UserResponse
| Authentication | Bearer header (not validated) | | Authorization | None — no manager role required (unlike auth API) | | Operations | Manage |
Path: user_id — UUID. No request body or query parameters.
31. Update password (admin)
POST /api/v3/user/v1/users/{user_id}/password — 204
| Authentication | Bearer header (not validated) | | Authorization | None — no manager role required | | Operations | Manage |
Path: user_id — UUID
Query parameters
| Param | Type | Required | Min length | Max length | Min value | Max value | Other |
|---|---|---|---|---|---|---|---|
new_password |
string | Yes | — | — | — | — | No schema validation; stored as provided (caller should pre-hash) |
No request body.
32. Log login event
POST /api/v3/user/v1/users/{user_id}/log-login — 204
| Authentication | Bearer header (not validated) | | Authorization | None | | Operations | Execute |
Path: user_id — UUID. No body or query params. Sets login_at and is_logout=false.
33. Log logout event
POST /api/v3/user/v1/users/{user_id}/log-logout — 204
| Authentication | Bearer header (not validated) | | Authorization | None | | Operations | Execute |
Path: user_id — UUID. No body or query params. Sets logout_at and is_logout=true.
34. Database health check
GET /api/v3/user/v1/users/test — 200 { "db_ok": true }
| Authentication | None (public) | | Authorization | None | | Operations | Execute (health) |
Related events & side effects
| Action | Database / state changes |
|---|---|
| Login | _login_at updated, is_logout=false |
| Logout | Refresh invalidated, _logout_at updated |
| Register | New core_user row, user_type=1000, password hashed (PBKDF2) |
| Email activation | email_verified=true, token cleared |
| Password change | password hash updated |
| Soft delete | is_active=false |
| Ban (auth API) | is_banned toggled |
Email / SMS: Activation and reset codes are generated server-side; email delivery is TODO in several paths. In debug mode, codes are returned in the API response.
Quick reference — all endpoints
Legend: [Public] = no auth · [JWT] = validated access token · [Bearer] = header only, not validated · [Mgr+] = Manager or System Manager · [Staff+] = System Manager, Manager, or Office Employee · [Self] = own account only
POST /api/v3/auth/login [Public]
POST /api/v3/auth/register [Public]
POST /api/v3/auth/token/refresh [Public]
POST /api/v3/auth/token/verify [Public]
POST /api/v3/auth/logout [JWT] Any role
POST /api/v3/auth/password/change [JWT] [Self]
POST /api/v3/auth/password/set [JWT] [Mgr+]
POST /api/v3/auth/password/reset [Public]
POST /api/v3/auth/password/reset/confirm [Public]
POST /api/v3/auth/activation/send [Public]
POST /api/v3/auth/activation/confirm [Public]
POST /api/v3/auth/user/activate [JWT] [Mgr+]
POST /api/v3/auth/user/ban [JWT] [Mgr+]
POST /api/v3/auth/user/create [JWT] [Staff+] role limits apply
GET /api/v3/users/profile/ [JWT] [Self]
PATCH /api/v3/users/profile/ [JWT] [Self]
GET /api/v3/users/profile/me [JWT] [Self]
POST /api/v3/users/profile/avatar [JWT] [Self] 501
POST /api/v3/user/v1/users/ [Bearer] no role check
POST /api/v3/user/v1/users/bulk [Bearer] no role check
GET /api/v3/user/v1/users/ [Public]
GET /api/v3/user/v1/users/{user_id} [Bearer] no role check
GET /api/v3/user/v1/users/email/{email} [Bearer] no role check
GET /api/v3/user/v1/users/mobile/{mobile} [Bearer] no role check
PUT /api/v3/user/v1/users/{user_id} [Bearer] no role check
PATCH /api/v3/user/v1/users/{user_id} [Bearer] no role check
PUT /api/v3/user/v1/users/bulk [Bearer] no role check
DELETE /api/v3/user/v1/users/{user_id} [Bearer] no role check
DELETE /api/v3/user/v1/users/{user_id}/hard [Bearer] no role check
POST /api/v3/user/v1/users/{user_id}/verify-email [Bearer] no role check
POST /api/v3/user/v1/users/{user_id}/password [Bearer] no role check
POST /api/v3/user/v1/users/{user_id}/log-login [Bearer] no role check
POST /api/v3/user/v1/users/{user_id}/log-logout [Bearer] no role check
GET /api/v3/user/v1/users/test [Public]