Repository navigation
[Enhancement]: Add dedicated media progress endpoint filtered by library item ID #5276
Description
Type of Enhancement
Server Backend
Describe the Feature/Enhancement
Type of Enhancement
Server Backend / API
Describe the feature/enhancement
Please add a dedicated endpoint for fetching media progress for a specific library item, for example:
GET /api/progress?libraryItemId=<libraryItemId>
Currently, clients need to call GET /api/me, read the full authenticated user payload, and then filter mediaProgress locally for a single item. The API docs show that /api/me includes the full user object with mediaProgress, bookmarks, permissions, and other user/server-related data, so this is much heavier than necessary for item-specific progress loading. [page:1]
Current behavior
Client code currently has to do something like this:
val progress =
dataRepository
.fetchUserInfoResponse()
.fold(
onSuccess = { it.mediaProgress ?: emptyList() },
onFailure = { emptyList() },
)
if (progress.isEmpty()) {
return@async null
}
progress
.filter { it.libraryItemId == bookId }
.filterNot { it.episodeId == null }
.sortedByDescending { it.lastUpdate }
.distinctBy { it.episodeId }Under the hood this goes through:
@GET("api/me")
suspend fun fetchUserInfo(): Response<UserResponse>So when opening a single item page, the client has to fetch all user progress entries and filter them locally for just one libraryItemId.
Problem
This is inefficient because:
- the payload is much larger than needed for a single item;
- client performance gets worse as
mediaProgressgrows; - item-specific UI depends on a general-purpose user info endpoint;
- the client has to do extra filtering, sorting, and deduplication work after downloading unrelated data.
This is especially noticeable for users with large listening histories or many podcast episode progress entries.
Proposed API
A dedicated endpoint such as:
GET /api/progress?libraryItemId=<libraryItemId>
Possible behavior:
- when
libraryItemIdis provided, return only progress entries for that item; - optionally support additional filtering such as
episodeOnly=1for podcast/item detail use cases; - optionally, without query params, return all progress entries for the authenticated user if that is useful for backward-compatible generic usage.
Example response:
{
"mediaProgress": [
{
"id": "progress_id",
"libraryItemId": "item_id",
"episodeId": "episode_id",
"duration": 1454.18449,
"progress": 0.434998929881311,
"currentTime": 632.568697,
"isFinished": false,
"hideFromContinueListening": false,
"lastUpdate": 1668586015691,
"startedAt": 1668120083771,
"finishedAt": null
}
]
}Alternative
An item-scoped route would also work well, for example:
GET /api/items/{libraryItemId}/progress
That may be more REST-friendly, especially since the requested data is tied to a single library item.
Prior art in the API
Audiobookshelf already has a more specific progress-related endpoint, GET /api/me/items-in-progress, which suggests that splitting progress use cases out from /api/me is already an accepted API direction. [page:2]
Use case
In mobile clients, item detail screens may need only the progress entries for one book or podcast, often to show episode-level listening state sorted by most recent activity. Fetching the full /api/me response just to derive that subset is unnecessarily heavy.
Why would this be helpful?
- reduces payload size for item detail requests;
- improves load time for clients that only need progress for one item;
- avoids coupling item progress UI to
/api/me; - provides a clearer API contract for progress as a resource.
Future Implementation (Screenshot)
no UI
Audiobookshelf Server Version
v2.35.1
Current Implementation (Screenshot)
No response
Activity
Vito0912 commentedon May 30, 2026
I am not sure if I understand your issue correctly, but that is well doable and also stated in the not so up to date API docs:
https://api.audiobookshelf.org/#get-a-media-progress
If you mean to get the progress for all users for a library item that is another topic, but as yous state
This does not to seem your intent
GrakovNe commentedon May 30, 2026
The problem is with podcasts.
If I call:
GET /api/me/progress/{libraryItemId}I only get a single media progress entry, for example:
{ "id": "ee1d6453-835e-4e56-8029-19dade1d32b2", "userId": "f6891cb5-3527-46fb-bdb3-d690e279382d", "libraryItemId": "dc4d3bfb-b952-4154-a52f-aecde9ba4292", "episodeId": "0025b1fe-9645-402f-ba8e-1c57aa2ab769", "mediaItemId": "0025b1fe-9645-402f-ba8e-1c57aa2ab769", "mediaItemType": "podcastEpisode", "duration": 77.208, "progress": 0.0000129520, "currentTime": 0.001, "isFinished": false, "hideFromContinueListening": false, "ebookLocation": null, "ebookProgress": 0, "lastUpdate": 1780147091360, "startedAt": 1780147091360, "finishedAt": null }But if I call:
GET /api/meI can see that the latest saved progress for the same
libraryItemIdis actually a different episode:{ "id": "cfef04de-3ace-4387-a0ca-f78bb41b260c", "userId": "f6891cb5-3527-46fb-bdb3-d690e279382d", "libraryItemId": "dc4d3bfb-b952-4154-a52f-aecde9ba4292", "episodeId": "475fb32b-a3e2-4072-91a9-c0eb3720bfe5", "mediaItemId": "475fb32b-a3e2-4072-91a9-c0eb3720bfe5", "mediaItemType": "podcastEpisode", "duration": 1545.264, "progress": 0.31196049865912884, "currentTime": 482.061328, "isFinished": false, "hideFromContinueListening": false, "ebookLocation": null, "ebookProgress": 0, "lastUpdate": 1780153963627, "startedAt": 1772461678448, "finishedAt": null }Lissen does not show podcast episodes as separate sub-items with their own detail pages. Instead, it shows them as a continuous playback queue so the user can listen through the podcast sequentially.
Because of this, I cannot afford to call the progress endpoint for every single episode – that would turn into an N+1 problem.
Instead, right now I fetch the entire user progress history once via
/api/meand then filtermediaProgressby thelibraryItemIdI currently need.That is why I want a dedicated endpoint that returns all progress entries for the authenticated user for a single
libraryItemId(especially for podcasts), so I can avoid both downloading the full/api/mepayload and doing one request per episode.Quaiyyum commentedon Sep 29, 2026
I'd like to work on this.
The fix is straightforward: add an optional
?libraryItemId=query param to the existingGET /api/me/progressendpoint inMeController.getAllMediaProgress(). When the param is provided, filterreq.user.mediaProgressesbymp.extraData?.libraryItemIdbefore mapping to the old format. No new route needed — the existing endpoint stays backward-compatible when called without the param.Working on a PR now.