# Get Library Usage Statistics The `/stats` page's **Library tab** payload: media_items **inventory** aggregates (what is IN the library), not reading behavior. Mirrors the Audiobookshelf library dashboard's information architecture. **Endpoint**: `GET /api/stats/library-usage` **Auth**: Required (any authenticated user) ## Query Parameters | Parameter | Type | Required | Description | | ---------- | ------------- | -------- | --------------------------------------------------------------------------- | | library_id | string (UUID) | No | Scope to one library. **Omit** to aggregate across ALL of the requesting user's visible libraries. | ## Family-lens rule Every response is scoped to the requester's **visible-library set** (`library_visibility`, default-ALLOW). An explicit `library_id` OUTSIDE that set is a **403** — never a silent pass. Omitted `library_id` aggregates only visible libraries. ## Example Request ```http GET /api/stats/library-usage?library_id=8c8d0cee-1cca-4def-9a37-edad3a4d438b Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` ## Response (200 OK) ```json { "library_id": "8c8d0cee-1cca-4def-9a37-edad3a4d438b", "items": 743, "authors": 446, "narrators": 41, "total_size_bytes": 309237452800, "total_seconds": 291240, "total_pages": 0, "hours_mode": true, "genres": [ { "name": "Fantasy", "count": 142 } ], "top_authors": [ { "name": "Terry Pratchett", "count": 42 } ], "longest_items": [ { "media_item_id": "uuid", "title": "Gardens of the Moon", "author": "Steven Erikson", "cover_image_path": "/uploads/8c8d0cee/cover.jpg", "seconds": 93600, "pages": 0, "size": 1400000000 } ], "largest_items": [ { "media_item_id": "uuid", "title": "Gardens of the Moon", "author": "Steven Erikson", "cover_image_path": "/uploads/8c8d0cee/cover.jpg", "seconds": 93600, "pages": 0, "size": 1400000000 } ] } ``` ### Field semantics | Field | Meaning | | -------------------- | ---------------------------------------------------------------------------------------------------------------- | | `library_id` | `""` when the scope is the All-libraries aggregate. | | `items` | Healthy media items (archived / missing-scan rows never count). | | `authors` | DISTINCT non-empty `author` values. | | `narrators` | DISTINCT values unnested from the `narrators TEXT[]` column across the scope. | | `total_size_bytes` | SUM of `file_size`. | | `total_seconds` | SUM of `audio_metadata.duration_seconds` — real audio hours; 0 for text libraries. | | `total_pages` | SUM of `page_count` — meaningful for text libraries; 0 for audiobooks. | | `hours_mode` | `true` when audio-duration data exists in scope: the client renders the time card in HOURS and ranks Longest Items by `seconds`. `false`: the time card renders in PAGES and Longest Items ranks by `pages`. The client decides — the payload always carries both sums. | | `genres` | Top 5 non-empty `genre` by item count (ties break alphabetically). | | `top_authors` | Top 10 non-empty `author` by item count (ties break alphabetically). | | `longest_items` | Top 5 by `seconds` (hours mode) or `pages` (pages mode). | | `largest_items` | Top 5 by `file_size` — works for every library type. | ### Absent data `Audio Tracks` (Audiobookshelf's per-file count) is deliberately absent: multi-file audiobooks (folder-of-tracks / zip books) now ingest as ONE item whose track list lives in `audio_metadata.tracks`, so a file count would still not equal a per-book "tracks" metric without a new query. Add it by aggregating `jsonb_array_length(audio_metadata->'tracks')` if the panel is ever wanted. ## Error Responses | Code | Description | | ---- | ------------------------------------------------------ | | 400 | `library_id` is not a valid UUID | | 401 | Invalid or expired token | | 403 | Library not in the user's visible set | | 500 | Aggregation failure |