API Reference
The iOS app runs on this JSON API, and so can anything else you write. Point the playground at your own server to send a real request to every endpoint below.
Registration, login and token refresh are public. Every other
/api/v1 endpoint needs an
Authorization: Bearer token — run
Login below and the playground copies the
access token into the field here.
Requests go straight from your browser, so your server's CORS
policy (CLIENT_URL) has to allow this origin. Use
HTTPS for a remote server: this page is served over HTTPS, and
browsers block plain HTTP requests from it. Loopback addresses are
the exception, though the details vary by browser.
Health
/live Liveness probe. No authentication required.
{ "result": "alive" } /ready Readiness probe — 503 until Postgres answers. No authentication required.
{ "result": "ready" } /health Plain-text heartbeat, answered by middleware ahead of the router. Accepts HEAD as well as GET. No authentication required.
. Users
/api/v1/users/registrations Create a new account.
/api/v1/users/sessions Log in. After a successful login the playground copies the access token into the Token field above.
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
} /api/v1/users/tokens Exchange a refresh token for a new token pair.
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
} Account
/api/v1/accounts/me The authenticated user.
/api/v1/accounts Update profile and appearance preference.
/api/v1/accounts/stats Library counts, watch-time totals and watch activity for the selected period.
week, month and year mean the current calendar week, month and year, not a rolling window. Inside one of them, want counts what was added rather than the whole list, and the watching and playing counts are omitted. Defaults to all.
{
"period": "all",
"movies": { "want": 131, "watched": 895, "minutes": 104760 },
"series": { "want": 16, "watching": 75, "watched": 96 },
"episodes": { "watched": 4213, "minutes": 168520 },
"games": { "want": 12, "playing": 2, "played": 34, "minutes": 61200 },
"activity": [
{ "date": "2025-01-01", "movieMinutes": 12480, "tvMinutes": 41320 },
{ "date": "2026-01-01", "movieMinutes": 9260, "tvMinutes": 33970 }
]
} Movies
/api/v1/movies Your movie library, one state at a time. type defaults to want.
{
"data": [
{
"id": 693134,
"title": "Dune: Part Two",
"posterPath": "/czembW0Rk1Ke7lCJGahbOhdCLZ.jpg",
"pinned": true,
"state": "want"
}
],
"meta": { "page": 1, "per": 24, "total": 1 }
} /api/v1/movies Add a movie to the library. id is the TMDB movie id.
/api/v1/movies/{id} Movie details, credits, and recommendations.
/api/v1/movies/{id} Change state or pin the movie.
/api/v1/movies/{id} Remove the movie from the library.
Games
/api/v1/games Your game library, one state at a time. type defaults to want.
/api/v1/games Add a game to the library. id is the IGDB game id, and posterPath is an IGDB cover image_id rather than a path.
/api/v1/games/{id} Game details from IGDB, with similar games and IGDB's estimated time for a main playthrough and for 100% completion, both in minutes.
/api/v1/games/{id} Change state or pin the game.
/api/v1/games/{id} Remove the game from the library.
TV Series
/api/v1/series Your series library, one state at a time. type defaults to want.
/api/v1/series Add a show to the library. id is the TMDB series id.
status is TMDB's own, and it matters: a show left at Returning Series never derives to watched, however many episodes are ticked.
/api/v1/series/{id} Series details with seasons.
/api/v1/series/{id} Change state or pin the show.
/api/v1/series/{id} Remove the show from the library.
/api/v1/series/{id}/season/{seasonNumber} Season details and episodes.
/api/v1/series/{id}/season/{seasonNumber}/episode/{episodeNumber} Single episode details.
Progress
/api/v1/series/{id}/progress Watched seasons and episodes for a show.
/api/v1/series/{id}/watched Mark a show watched. Ids come from the URL, and the body carries the rows to write.
Only the seasons and episodes you list are recorded, so a body without them marks nothing. The example lists two episodes of season one; a real client sends all nine, and every other season too.
/api/v1/series/{id}/watched Unmark the whole show, dropping every season and episode recorded for it.
/api/v1/series/{id}/seasons/{seasonId}/watched Mark a season watched.
The episodes array is what actually gets recorded. Send the season's episodes in it — without them the show ends up with no watched episodes, and nothing is tracked.
/api/v1/series/{id}/seasons/{seasonId}/watched Unmark a season and its episodes.
/api/v1/series/{id}/seasons/{seasonId}/episodes/{episodeId}/watched Mark an episode as watched.
The show and season rows are created from this body if they don't exist yet, so send their metadata alongside the episode.
/api/v1/series/{id}/seasons/{seasonId}/episodes/{episodeId}/watched Unmark an episode.
Search & Trending
/api/v1/search/movies Search movies. query is required.
/api/v1/search/series Search TV shows. query is required.
/api/v1/search/games Search PlayStation 5 and PlayStation 4 games. query is required.
/api/v1/search/people Search people. query is required.
/api/v1/trending/movies Trending movies.
/api/v1/trending/series Trending TV shows.
/api/v1/trending/games Trending PlayStation games.
/api/v1/trending/people Trending people.
Up Next
/api/v1/up-next The next aired episode you haven't watched of each pinned show you're watching, plus released movies and games from your pinned want lists.
{
"episodes": [
{
"seriesId": 125988,
"seriesTitle": "Silo",
"seriesPosterPath": "/gMYZZvnkVNTqSVnVCphWbPXwWwb.jpg",
"id": 7173957,
"title": "Who Are You?",
"seasonNumber": 3,
"number": 1,
"posterPath": "/nIvrv95n7gukebcCEUeZJDy9iEp.jpg",
"overview": "Juliette wakes somewhere she was never meant to reach…",
"runtime": 56,
"rating": 6.6,
"airDate": "2026-07-02"
}
],
"movies": [
{
"id": 786892,
"title": "Furiosa: A Mad Max Saga",
"posterPath": "/iADOJ8Zymht2JPMoy3R7xceZprc.jpg",
"overview": "As the world falls, young Furiosa is snatched from the Green Place…",
"runtime": 149,
"rating": 7.4,
"releaseDate": "2024-05-22"
}
],
"games": [
{
"id": 1942,
"title": "The Witcher 3: Wild Hunt",
"posterPath": "co1wyy",
"overview": "A story-driven open world RPG set in a fantasy universe…",
"runtime": 3000,
"rating": 9.5,
"releaseDate": "2015-05-19"
}
]
} People
/api/v1/people/{id} Person details and notable credits.
Reference
Common errors
A failure inside a controller returns a JSON body with an
error message and one of these status codes. Responses
from the middleware around it — rate limiting, an unmatched route —
are plain text.
| Status | Meaning | When |
|---|---|---|
400 | Bad Request | Malformed JSON, an unparseable id, or a body that fails validation |
401 | Unauthorized | Missing, invalid or expired access token, or wrong login credentials |
409 | Conflict | Registering a login or email that is already taken |
422 | Unprocessable Entity | The request was well formed but the work failed — including a title the upstream provider doesn't have |
429 | Too Many Requests | More than 5 requests a minute per IP to the public /users routes, or 120 a minute per user everywhere else |
500 | Internal Server Error | Registration failed for a reason the API won't detail |
503 | Service Unavailable | A dependency is unreachable, from /ready or from the auth middleware |
There is no 404: a title that isn't in your library, or
that TMDB and IGDB can't resolve, comes back as 422.