Files
bookhoard/docs/developer/api/admin/list_users.md
T
John O'Keefe cc5677eb17 docs(auth,users,devices,progress,matching): Beam batch 8 — WO5 backfill complete
14 endpoints backfilled: devices pending/approve/reject; users
password/profile/profile-delete self-service (admin variants in prose);
admin list_users + update_user_max_devices; progress history; book
matching; new password-rules doc.

Gate fix: authLabel missed JWT middleware written inline as
createJWTMiddleware(cfg) (uppercase 'JWT') — reported auth 'none' for
/api/auth self-service routes and would have for any wrapper-inlined
group. Labeled 'JWTMiddleware' + fallthrough 'jwt' case.

Claude registry: 12 entries. Drift fixed by gate: list_users on-device —
handler returns raw []database.Users (bare array, full model incl.
password_hash — real leak the API emits; pinned + documented; earlier
draft had invented {users,total,limit,offset} wrapper). max-devices
response is {message} only. books/match is GET /api/books/match
{matches, action}. progress history sessions = ReadingHistory rows
(pgtype fields). make test 9/9.
2026-10-24 17:33:32 -04:00

3.2 KiB

method, path, auth, area
method path auth area
GET /api/auth/users jwt+admin authentication

List Users

List all users in the system (admin only).

Endpoint: GET /api/auth/users Auth: Required (Admin only)

Query Parameters

Parameter Type Required Description
limit integer No Maximum number of users to return (default: 50)
offset integer No Number of users to skip (default: 0)
search string No Search by email or username

Request Headers

Header Type Required Description
Authorization string Yes Bearer token (must have admin role)

Example Request

GET /api/auth/users?limit=10&search=john
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response (200 OK)

[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "email": "user@example.com",
    "username": "johndoe",
    "password_hash": "$2a$04$REDACTED",
    "first_name": "John",
    "last_name": "Doe",
    "role": "user",
    "theme": "tokyo-night",
    "max_devices": 10,
    "created_at": "2026-02-08T10:00:00Z",
    "timezone": "America/New_York",
    "updated_at": "2026-02-08T10:00:00Z",
    "default_audio_speed": 1
  }
]

Response is a bare JSON array of full user rows — no pagination wrapper and no device_count (those were invented in an earlier draft).

Leak note: the user model json-marshals password_hash verbatim — the list-users response exposes password hashes to any admin caller. Known defect, tracked separately (doc mirrors the real field set; do not implement against a redacted shape).

Response Fields

Field Type Description
id string User ID (UUID)
email string Email address
username string Username
first_name string First name (optional)
last_name string Last name (optional)
role string User role ("user" or "admin")
theme string Theme preference (optional)
max_devices integer Maximum number of devices allowed
device_count integer Current number of registered devices
created_at string Account creation timestamp (ISO 8601)
updated_at string Last update timestamp (ISO 8601)
total integer Total number of users matching query
limit integer Limit applied to this request
offset integer Offset applied to this request

Error Responses

Code Description
401 Invalid or expired token
403 User does not have admin privileges