--- title: Users API --- # User & Authentication API Complete reference for authentication, profile, and user management endpoints. **OpenAPI tag groups:** `authentication`, `profile`, `users` --- ## Table of contents 1. [Overview](#overview) 2. [User types & roles](#user-types--roles) 3. [Authentication & authorization](#authentication--authorization) 4. [Authentication flow](#authentication-flow) 5. [Common errors](#common-errors) 6. [Field validation & constraints](#field-validation--constraints) 7. [Query, filtering, sorting & pagination](#query-filtering-sorting--pagination) 8. [Authentication APIs (`/api/v3/auth`)](#authentication-apis-apiv3auth) 9. [Profile APIs (`/api/v3/users/profile`)](#profile-apis-apiv3usersprofile) 10. [User CRUD APIs (`/api/v3/user/v1/users`)](#user-crud-apis-apiv3userv1users) 11. [Related events & side effects](#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 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 ``` Obtain `` 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 ```bash # System Manager sets another user's password curl -X POST http://127.0.0.1:8000/api/v3/auth/password/set \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"user": "550e8400-e29b-41d4-a716-446655440000", "password": "newpass12", "re_password": "newpass12"}' # → 200 { "message": "Password set successfully" } ``` ```bash # Customer reads own profile curl http://127.0.0.1:8000/api/v3/users/profile/ \ -H "Authorization: Bearer " # → 200 ProfileResponse (always the JWT holder's profile) ``` ```bash # Office Employee creates a customer curl -X POST http://127.0.0.1:8000/api/v3/auth/user/create \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"email": "new@example.com", "first_name": "Ann", "last_name": "Lee", "user_type": 1000}' # → 201 RegisterResponse ``` ```bash # Unauthenticated user lists active users (public) curl "http://127.0.0.1:8000/api/v3/user/v1/users/?limit=10" # → 200 UserListResponse ``` #### Unauthorized ```bash # Customer attempts to ban another user curl -X POST http://127.0.0.1:8000/api/v3/auth/user/ban \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"user": "550e8400-e29b-41d4-a716-446655440000", "status": true}' # → 403 { "detail": "Only managers can ban/unban users" } ``` ```bash # Manager attempts to create a System Manager curl -X POST http://127.0.0.1:8000/api/v3/auth/user/create \ -H "Authorization: Bearer " \ -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" } ``` ```bash # Request profile without token curl http://127.0.0.1:8000/api/v3/users/profile/ # → 403 { "detail": "Invalid authorization code." } ``` ```bash # 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 " # → 403 { "detail": "Invalid token type. Access token required." } ``` ```bash # 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 ```mermaid sequenceDiagram participant Client participant API participant DB Client->>API: POST /api/v3/auth/login {email, password} API->>DB: Verify user + password DB-->>API: User record API-->>Client: {access, refresh, exp_time, user_type} Client->>API: GET /api/v3/users/profile/ (Bearer access) API-->>Client: ProfileResponse Note over Client,API: When access expires Client->>API: POST /api/v3/auth/token/refresh {refresh} API-->>Client: {access} ``` --- ## 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](#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**: ```json { "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):** 1. **Pydantic / FastAPI `Query`** → **422** on failure (body or query params) 2. **Service / business logic** → **400** or **401** with `detail` message 3. **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** ```bash # 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** ```bash 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"}' ``` ```json { "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** | — | ```json { "access": "" } ``` **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](#create-user--role-specific-authorization-post-apiv3authusercreate)) | | **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 an `Authorization: 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](#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** | ```json { "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] ```