Scrob - Self-hosted media tracking app. It syncs your libraries from Jellyfin, Plex, and Emby, tracks your watch history, ratings, and personal lists - Your own private Letterboxd + Trakt
451
stars
524
commits
Python
primary language
Sep 10, 2026
updated
Scrob syncs your libraries from Jellyfin, Plex, Emby, Nuvio, ARVIO, and Stremio, tracks your watch history, ratings, and personal lists, and can push watched activity back to connected providers - all from a clean, app-like web interface that installs as a PWA on any device.
/docs (Swagger UI) and /redoc (ReDoc), useful if you're scripting against Scrob directly.
Dashboard

Explore

Movie

Show

Season

Episode

Search

History (mobile)

Lists (mobile)

Settings

Images are hosted on Docker Hub (
bellamy/scrob). A mirror is also available on GHCR (ghcr.io/ellite/scrob) if you prefer.
curl -o docker-compose.yaml https://raw.githubusercontent.com/ellite/scrob/main/docker-compose.yaml
docker-compose.yaml and replace the required values:services:
scrob-db:
container_name: scrob-db
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: scrob
POSTGRES_PASSWORD: changeme # ← change this
POSTGRES_DB: scrob
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U scrob -d scrob"]
interval: 5s
timeout: 5s
retries: 10
scrob:
container_name: scrob
image: bellamy/scrob:latest
restart: unless-stopped
depends_on:
scrob-db:
condition: service_healthy
ports:
- "7330:7330"
environment:
DATABASE_URL: postgresql+asyncpg://scrob:changeme@scrob-db:5432/scrob # ← match password above
SECRET_KEY: changeme # ← generate with: openssl rand -hex 32
TZ: UTC
volumes:
- scrob_data:/app/backend/data
volumes:
db_data:
scrob_data:
docker compose up -d
The omnibus image bundles PostgreSQL inside the container - no separate database service needed. It's the simplest way to get started, especially on platforms like Unraid or Portainer where managing multiple containers is cumbersome.
Image tags:
bellamy/scrob:latest-omnibus/ghcr.io/ellite/scrob:latest-omnibus
curl -o docker-compose.yml https://raw.githubusercontent.com/ellite/scrob/main/docker-compose.omnibus.yml
SECRET_KEY:SECRET_KEY: changeme # ← generate with: openssl rand -hex 32
docker compose up -d
That's it - no database container, no DATABASE_URL to configure. PostgreSQL is initialised automatically on first run and persisted in the scrob_db volume.
Switching to an external database later: set DATABASE_URL in the environment and the embedded PostgreSQL will be skipped entirely. The omnibus image behaves identically to the standard image when DATABASE_URL is provided.
Note: The embedded PostgreSQL version is tied to the image's base OS (Debian Bookworm ships PostgreSQL 15). Major version upgrades of the bundled database require a manual data migration. If you anticipate needing to control the database version independently, use the standard two-container setup instead.
Standard image (requires a separate PostgreSQL container):
# Create a dedicated network
docker network create scrob-net
# Start the database
docker run -d \
--name scrob-db \
--network scrob-net \
--restart unless-stopped \
-e POSTGRES_USER=scrob \
-e POSTGRES_PASSWORD=changeme \
-e POSTGRES_DB=scrob \
-v scrob_db:/var/lib/postgresql/data \
postgres:16-alpine
# Start Scrob
docker run -d \
--name scrob \
--network scrob-net \
--restart unless-stopped \
-p 7330:7330 \
-e DATABASE_URL="postgresql+asyncpg://scrob:changeme@scrob-db:5432/scrob" \
-e SECRET_KEY="$(openssl rand -hex 32)" \
-e TZ=UTC \
-v scrob_data:/app/backend/data \
bellamy/scrob:latest
Omnibus image (PostgreSQL included - no separate container needed):
docker run -d \
--name scrob \
--restart unless-stopped \
-p 7330:7330 \
-e SECRET_KEY="$(openssl rand -hex 32)" \
-e TZ=UTC \
-v scrob_data:/app/backend/data \
-v scrob_db:/app/postgres/data \
bellamy/scrob:latest-omnibus
http://localhost:7330 and create your account.For Nuvio, sign in and select one of the returned profiles. For Stremio, select Connect Stremio, then authorize the generated Link code or QR code in your Stremio account. See Nuvio Cloud Synchronization and Stremio Synchronization for provider-specific behavior and limitations.
docker compose pull && docker compose up -d
Database migrations run automatically on startup - no manual steps required.
| Variable | Default | Description |
|---|---|---|
SECRET_KEY | - | Required. JWT signing key. Generate with openssl rand -hex 32. |
DATABASE_URL | - | Required (standard image). PostgreSQL connection string (postgresql+asyncpg://...). Optional on the omnibus image - if omitted, the embedded database is used. |
ENABLE_REGISTRATIONS | false | Allow new users to register. The first user can always register regardless of this setting. |
REGISTRATION_MAX_ALLOWED_USERS | 0 | Maximum number of registered users. 0 = unlimited. |
TZ | UTC | Container timezone (e.g. Europe/Lisbon). |
PUID | 1000 | User ID to run the process as. |
PGID | 1000 | Group ID to run the process as. |
BACKEND_PORT | 7331 | Internal port the backend binds to. Override only if 7331 conflicts on bare metal. |
OIDC_ENABLED | false | Enable OIDC login. |
OIDC_DISABLE_PASSWORD_LOGIN | false | Enforce OIDC-only login (disables username/password). |
See docker-compose.yaml for the full list of OIDC variables and other variables.
Scrob listens on port 7330. Place a reverse proxy (Caddy, Nginx, Traefik) in front for HTTPS - required for the PWA install prompt on non-localhost addresses.
# Caddyfile
scrob.yourdomain.com {
reverse_proxy localhost:7330
}
Remove the scrob-db service and set DATABASE_URL to your existing instance:
DATABASE_URL: postgresql+asyncpg://user:password@your-db-host:5432/scrob
TMDB covers the great majority of titles. A TheTVDB key is optional and only affects TVDB-specific paths:
With no key configured, all of the above fall back to TMDB-only behaviour; nothing else is affected.
Which credential to get. TheTVDB v4 has two key types:
| Type | Where | Auth | Notes |
|---|---|---|---|
| Free project key | thetvdb.com/api-information | API key only | Free for projects under the revenue threshold in TheTVDB's terms. Requires attribution — Scrob displays a "Metadata provided by TheTVDB" link on every TVDB-sourced page and in About. |
| Subscriber-supported key | TheTVDB dashboard | API key + subscriber PIN | Enter both the key and the PIN in Scrob. |
Configure it per-user in Settings → General → TVDB API Key (with the Subscriber PIN field for a subscriber key), or server-wide in Admin → Settings → TVDB as a fallback for all users. Use Test key to verify the pair before saving.
Rotation / revocation. Scrob caches the TheTVDB login token in memory for up to 29 days per key. After changing or removing the key or PIN in settings, the new credential takes effect on the next lookup; a stale token for the old credential is discarded on restart.
To enable RatingPosterDB posters, enter your RPDB API key in Settings → General → RPDB API Key, test it, and save. This is a personal display preference: it applies only to your browsing, including when viewing another user's public profile or list. Clear the key and save to return to the original artwork.
Choose poster styles and rating sources in the RPDB manager; available customizations depend on your RPDB plan. Scrob uses RPDB's account-default posters and falls back to the original artwork when an RPDB image cannot load. Movie and show portraits, including parent-show portraits in history and Next Up, can use RPDB. Episode stills, season-specific artwork, backdrops, people and collection artwork stay unchanged.
Rating posters are fetched server-side through the same image proxy as TMDB and TheTVDB artwork, so your key stays on the server and is never placed in the page. It is not written into shared media metadata, and data exports include it only when you explicitly select API Keys. Existing metadata and the server's image cache are unchanged.
Scrob supports pull synchronization from ARVIO Cloud (https://auth.arvio.tv/.netlify/functions), importing watched movies, watched episodes, and continue watching playback progress per profile.
| Variable | Default | Description |
|---|---|---|
ARVIO_APP_ANON_KEY | (Official embedded key) | Public anon API key for auth.arvio.tv. The official key is embedded by default. |
The GitHub Actions workflow (.github/workflows/docker-x64.yml) automatically validates the embedded ARVIO_APP_ANON_KEY against auth.arvio.tv during every container build, ensuring builds fail immediately with a GitHub workflow error if the public key is ever rotated.
Scrob connects to the Nuvio public Cloud API at https://api.nuvio.tv by default and also supports self-hosted Nuvio backends. A TMDB Read Access Token must be configured in Scrob so Nuvio content identifiers can be matched to movies and shows.
Scrob exchanges the email and password for a refresh token. The password is never persisted. Refresh-token rotation is handled automatically during connection checks and synchronization.
Each connection targets one Nuvio profile. Add another connection if you need to synchronize another profile from the same account.
Set NUVIO_APP_ANON_KEY to the anon/public key for your self-hosted Nuvio Supabase project, then enter that project's URL in the editable Cloud API URL field when adding the connection. If the variable is unset, Scrob continues to use the official Nuvio publishable key.
| Direction | Setting | Behavior |
|---|---|---|
| Nuvio → Scrob | Collection status | Imports the profile's library movies and series. |
| Nuvio → Scrob | Watched status | Imports watched movies and episodes with their latest watch timestamps. |
| Nuvio → Scrob | Playback progress | Imports position and duration into Continue Watching. |
| Scrob → Nuvio | Collection status | Adds or removes library membership while preserving unrelated Nuvio items. |
| Scrob → Nuvio | Watched status | Pushes watched and unwatched changes made in Scrob or imported from another connected provider. |
| Scrob → Nuvio | Playback progress | Pushes current playback positions into Nuvio's Continue Watching state as non-destructive upserts. |
Sync now runs an inbound synchronization using the enabled Nuvio → Scrob settings. Push sends the enabled collection, watched-history, and playback-progress data from Scrob to Nuvio. Pushes use merge semantics and preserve unrelated remote items.
Ratings are not synchronized with Nuvio.
Auto Pull and Auto Push can run independently every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours. Nuvio synchronization is polling-based; Nuvio does not use the media-server webhook URLs documented below.
Inbound Nuvio identifiers are normalized to TMDB for Scrob's internal matching. Before an outbound push, Scrob resolves those TMDB identifiers to Nuvio-compatible bare IMDb identifiers (tt...) and caches the mapping. Unsupported identifiers are skipped rather than attached to the wrong title.
Trakt now requires a Trakt VIP subscription to create a new API application (the client ID/secret used below) - a restriction Trakt introduced on their end, not a Scrob limitation. There are two ways to get your Trakt data into Scrob depending on whether you have VIP:
| Requires VIP | Imports | Pushes Scrob → Trakt | |
|---|---|---|---|
| OAuth connection | Yes (to create the API app) | Watched history, ratings, lists - kept in sync automatically | Yes - watched status, ratings, collection, lists, live "now watching" |
| Export import | No | Watched history, ratings (including per-episode), lists - one-time snapshot per upload | No - pull only |
Re-uploading a newer export is safe to do any time you want to catch up on new activity - imported watch plays and ratings are deduplicated, so nothing is imported twice.
Auto Pull and Auto Push apply only to the OAuth connection and can run independently every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours.
Scrob can import a one-time CSV export from Yamtrack or its fork Floppy.
.csv file on the upload box.This is a pull-only, one-shot import, like the Trakt export path above - there's no ongoing sync or connection left behind afterward. Re-uploading a newer export is safe; imported items are deduplicated. Requires a TMDB Read Access Token configured in Scrob.
Scrob uses Stremio's account datastore API at https://api.strem.io, the official Link flow at https://link.stremio.com, and Cinemeta episode metadata. Configure a TMDB Read Access Token in Scrob before synchronizing so Stremio IMDb identifiers can be mapped to Scrob media.
Scrob never asks for or stores your Stremio password. The Link flow returns an account authorization key, which is stored server-side and redacted from frontend API responses. Deleting the connection logs out that Stremio session. Each Scrob user can have one Stremio connection.
Authorization links expire in the Scrob interface after 10 minutes. Select Connect Stremio again to generate a fresh code.
| Direction | Setting | Behavior |
|---|---|---|
| Stremio → Scrob | Collection status | Imports active Stremio library movies and series. |
| Stremio → Scrob | Watched status | Imports watched movies and episodes. Series episode state is decoded from Stremio's watched bitfield using Cinemeta episode order. |
| Stremio → Scrob | Playback progress | Imports the current movie or episode position and duration into Continue Watching. |
| Scrob → Stremio | Collection status | Adds local collection items and removes only items previously pushed by this Scrob connection. Items created directly in Stremio are preserved. |
| Scrob → Stremio | Watched status | Merges movie and episode watched state into the existing Stremio record. |
| Scrob → Stremio | Playback progress | Merges the current playback position, duration, and episode identifier into Stremio. |
Sync now performs an inbound pull. The first pull reads the complete Stremio library; later pulls use Stremio modification metadata with a five-minute overlap window. Push sends the complete set of enabled outbound data. Changes imported from another provider are also forwarded to Stremio when the corresponding outbound option is enabled.
Outbound writes first fetch the current Stremio record and preserve unknown fields, addon metadata, and unrelated remote items. No-op records are skipped. Ratings and Stremio addons are not synchronized.
Auto Pull and Auto Push use separate schedules and can run every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours.
Use Full resync when the incremental cursor must be rebuilt. It reads the complete Stremio library and reconciles only collection sources owned by that Stremio connection; collection entries still backed by Jellyfin, Plex, Emby, Nuvio, or another source remain in Scrob.
Stremio exposes a current watched state rather than Scrob's complete per-play history. For series, Stremio stores a watched-episode bitfield and one lastWatched timestamp for the item, so repeated episode plays and their individual timestamps cannot be reconstructed exactly. Playback progress represents one current movie or episode per library item.
Simkl uses PIN-based authentication - no client secret is needed.
| Direction | Setting | Behavior |
|---|---|---|
| Simkl → Scrob | Watched history | Imports watched movies and episodes. |
| Simkl → Scrob | Ratings | Imports ratings. |
| Simkl → Scrob | Lists / Watchlist | Imports "plan to watch" items into a managed Simkl - Watchlist list. |
| Scrob → Simkl | Watched status | Pushes watched and unwatched changes made in Scrob or imported from another connected provider. |
| Scrob → Simkl | Ratings | Pushes rating changes. |
| Scrob → Simkl | Live scrobbling | Pushes playback start/stop events from webhooks and manual scrobble sessions in real time. |
The manual Push action sends the complete enabled watched-history and ratings snapshot, in batches of 50 items per request. Collection membership and the Simkl watchlist are not pushed back to Simkl.
Auto Pull and Auto Push can run independently every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours.
The manual Push action sends the complete enabled watched, ratings, or managed-watchlist snapshot. MDBList pagination follows next_cursor and requests the documented maximum of 1,000 items per page.
Auto Pull and Auto Push can run independently every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours.
Webhooks update your watch history and Continue Watching in real time. Each user's webhook URL is shown in Connections next to the relevant integration.
# Jellyfin, Plex, Emby - connection_id is shown in Connections next to each server
https://your-scrob-url/api/proxy/webhooks/{jellyfin|plex|emby}/{connection_id}?api_key=YOUR_API_KEY
# Kodi - no connection, just the API key
https://your-scrob-url/api/proxy/webhooks/kodi?api_key=YOUR_API_KEY
Playback Start, Playback Progress, Playback Stop, User Data Saved (this is what fires when you manually mark something watched/unwatched - the plugin has no separate "Mark Played" event), Item Added, and Item Deleted (keeps your Scrob collection in sync with your library without waiting for playback or a full resync).Movies and Episodes.Do not use a custom template - Jellyfin's template engine produces invalid JSON. "Send all properties" sends a well-formed payload that Scrob parses correctly.
Plex webhooks require a Plex Pass subscription.
Playback Start, Playback Stop, Item Added, Item Deleted.Emby's webhook plugin has no separate "playback progress" event, so the Now Playing bar's live progress instead comes from Scrob polling Emby's own Sessions API in the background - no extra configuration needed.
Kodi scrobbling uses the scrob-kodi add-on - no manual webhook configuration needed.
your-scrob-url/link in a browser, sign in, and approve. This works with 2FA accounts, never exposes your password, and the device can be revoked on its own from Connections → Connected Apps.The POST /api/proxy/webhooks/kodi endpoint (and the kodi/history, kodi/ratings, kodi/rating helpers the add-on uses for library sync) accept either an Authorization: Bearer device token or the ?api_key= query parameter.
Scrob supports any OpenID Connect provider (Authelia, Authentik, Keycloak, Google, etc.).
OIDC_ENABLED: "true"
OIDC_PROVIDER_NAME: "Authelia"
OIDC_CLIENT_ID: "scrob"
OIDC_CLIENT_SECRET: "your-secret"
OIDC_AUTH_URL: "https://auth.yourdomain.com/api/oidc/authorization"
OIDC_TOKEN_URL: "https://auth.yourdomain.com/api/oidc/token"
OIDC_USERINFO_URL: "https://auth.yourdomain.com/api/oidc/userinfo"
OIDC_REDIRECT_URL: "https://scrob.yourdomain.com/oidc-callback"
# OIDC_LOGOUT_URL: "https://auth.yourdomain.com/api/oidc/logout" # your provider's logout endpoint
# OIDC_SCOPES: "openid email profile" # default shown - override only if your provider needs different scopes
# OIDC_IDENTIFIER_FIELD: "email" # userinfo field used to match/create the Scrob account - default shown
OIDC_AUTO_CREATE_USERS: "true"
# OIDC_DISABLE_PASSWORD_LOGIN: "true" # uncomment to enforce SSO-only
Register Scrob as a client in your provider with redirect URI: https://scrob.yourdomain.com/oidc-callback
Scrob can require new users to verify their email address before logging in. Providing SMTP settings also enables the forgot password link on the login page.
REQUIRE_EMAIL_VALIDATION: "true"
SERVER_URL: "https://scrob.yourdomain.com"
SMTP_ADDRESS: "smtp.gmail.com"
SMTP_PORT: "587"
SMTP_ENCRYPTION: "tls"
SMTP_USERNAME: "myemail@gmail.com"
SMTP_PASSWORD: "your-app-password"
FROM_EMAIL: "myemail@gmail.com"
| Variable | Default | Description |
|---|---|---|
REQUIRE_EMAIL_VALIDATION | false | Require new users to verify their email before logging in. |
SERVER_URL | - | Public URL of your Scrob instance, used to build the validation link in emails. |
SMTP_ADDRESS | - | SMTP server hostname. |
SMTP_PORT | 587 | SMTP server port. |
SMTP_ENCRYPTION | tls | Encryption method - tls or ssl. |
SMTP_USERNAME | - | SMTP login username. |
SMTP_PASSWORD | - | SMTP login password (use an app password if using Gmail). |
FROM_EMAIL | - | Address emails are sent from. |
Contributions are welcome - whether it's a bug report, a feature request, or a pull request.
/api/proxy/.Commit messages follow Conventional Commits - feat:, fix:, chore: - as releases and changelogs are generated automatically from them.
git clone https://github.com/ellite/scrob.git
cd scrob
# Start a local database
docker compose -f docker-compose-test-db.yaml up -d
# Copy and fill in the environment file
cp .env.example .env
# Edit .env - set POSTGRES_* and SECRET_KEY at minimum
cd backend
uv sync
uv run alembic upgrade head
uv run uvicorn main:app --reload --port 7331
cd frontend
npm install
npm run dev
The frontend dev server starts on http://localhost:4321 and proxies API calls to the backend on 7331.
Scrob is licensed under the GNU General Public License v3.0.
You are free to use, modify, and distribute Scrob, provided that any derivative works are also released under the GPLv3.
Python
64.4%
Astro
33.0%
TypeScript
2.0%
Scrob - Self-hosted media tracking app. It syncs your libraries from Jellyfin, Plex, and Emby, tracks your watch history, ratings, and personal lists - Your own private Letterboxd + Trakt
451
stars
524
commits
Python
primary language
Sep 10, 2026
updated
Scrob syncs your libraries from Jellyfin, Plex, Emby, Nuvio, ARVIO, and Stremio, tracks your watch history, ratings, and personal lists, and can push watched activity back to connected providers - all from a clean, app-like web interface that installs as a PWA on any device.
/docs (Swagger UI) and /redoc (ReDoc), useful if you're scripting against Scrob directly.
Dashboard

Explore

Movie

Show

Season

Episode

Search

History (mobile)

Lists (mobile)

Settings

Images are hosted on Docker Hub (
bellamy/scrob). A mirror is also available on GHCR (ghcr.io/ellite/scrob) if you prefer.
curl -o docker-compose.yaml https://raw.githubusercontent.com/ellite/scrob/main/docker-compose.yaml
docker-compose.yaml and replace the required values:services:
scrob-db:
container_name: scrob-db
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: scrob
POSTGRES_PASSWORD: changeme # ← change this
POSTGRES_DB: scrob
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U scrob -d scrob"]
interval: 5s
timeout: 5s
retries: 10
scrob:
container_name: scrob
image: bellamy/scrob:latest
restart: unless-stopped
depends_on:
scrob-db:
condition: service_healthy
ports:
- "7330:7330"
environment:
DATABASE_URL: postgresql+asyncpg://scrob:changeme@scrob-db:5432/scrob # ← match password above
SECRET_KEY: changeme # ← generate with: openssl rand -hex 32
TZ: UTC
volumes:
- scrob_data:/app/backend/data
volumes:
db_data:
scrob_data:
docker compose up -d
The omnibus image bundles PostgreSQL inside the container - no separate database service needed. It's the simplest way to get started, especially on platforms like Unraid or Portainer where managing multiple containers is cumbersome.
Image tags:
bellamy/scrob:latest-omnibus/ghcr.io/ellite/scrob:latest-omnibus
curl -o docker-compose.yml https://raw.githubusercontent.com/ellite/scrob/main/docker-compose.omnibus.yml
SECRET_KEY:SECRET_KEY: changeme # ← generate with: openssl rand -hex 32
docker compose up -d
That's it - no database container, no DATABASE_URL to configure. PostgreSQL is initialised automatically on first run and persisted in the scrob_db volume.
Switching to an external database later: set DATABASE_URL in the environment and the embedded PostgreSQL will be skipped entirely. The omnibus image behaves identically to the standard image when DATABASE_URL is provided.
Note: The embedded PostgreSQL version is tied to the image's base OS (Debian Bookworm ships PostgreSQL 15). Major version upgrades of the bundled database require a manual data migration. If you anticipate needing to control the database version independently, use the standard two-container setup instead.
Standard image (requires a separate PostgreSQL container):
# Create a dedicated network
docker network create scrob-net
# Start the database
docker run -d \
--name scrob-db \
--network scrob-net \
--restart unless-stopped \
-e POSTGRES_USER=scrob \
-e POSTGRES_PASSWORD=changeme \
-e POSTGRES_DB=scrob \
-v scrob_db:/var/lib/postgresql/data \
postgres:16-alpine
# Start Scrob
docker run -d \
--name scrob \
--network scrob-net \
--restart unless-stopped \
-p 7330:7330 \
-e DATABASE_URL="postgresql+asyncpg://scrob:changeme@scrob-db:5432/scrob" \
-e SECRET_KEY="$(openssl rand -hex 32)" \
-e TZ=UTC \
-v scrob_data:/app/backend/data \
bellamy/scrob:latest
Omnibus image (PostgreSQL included - no separate container needed):
docker run -d \
--name scrob \
--restart unless-stopped \
-p 7330:7330 \
-e SECRET_KEY="$(openssl rand -hex 32)" \
-e TZ=UTC \
-v scrob_data:/app/backend/data \
-v scrob_db:/app/postgres/data \
bellamy/scrob:latest-omnibus
http://localhost:7330 and create your account.For Nuvio, sign in and select one of the returned profiles. For Stremio, select Connect Stremio, then authorize the generated Link code or QR code in your Stremio account. See Nuvio Cloud Synchronization and Stremio Synchronization for provider-specific behavior and limitations.
docker compose pull && docker compose up -d
Database migrations run automatically on startup - no manual steps required.
| Variable | Default | Description |
|---|---|---|
SECRET_KEY | - | Required. JWT signing key. Generate with openssl rand -hex 32. |
DATABASE_URL | - | Required (standard image). PostgreSQL connection string (postgresql+asyncpg://...). Optional on the omnibus image - if omitted, the embedded database is used. |
ENABLE_REGISTRATIONS | false | Allow new users to register. The first user can always register regardless of this setting. |
REGISTRATION_MAX_ALLOWED_USERS | 0 | Maximum number of registered users. 0 = unlimited. |
TZ | UTC | Container timezone (e.g. Europe/Lisbon). |
PUID | 1000 | User ID to run the process as. |
PGID | 1000 | Group ID to run the process as. |
BACKEND_PORT | 7331 | Internal port the backend binds to. Override only if 7331 conflicts on bare metal. |
OIDC_ENABLED | false | Enable OIDC login. |
OIDC_DISABLE_PASSWORD_LOGIN | false | Enforce OIDC-only login (disables username/password). |
See docker-compose.yaml for the full list of OIDC variables and other variables.
Scrob listens on port 7330. Place a reverse proxy (Caddy, Nginx, Traefik) in front for HTTPS - required for the PWA install prompt on non-localhost addresses.
# Caddyfile
scrob.yourdomain.com {
reverse_proxy localhost:7330
}
Remove the scrob-db service and set DATABASE_URL to your existing instance:
DATABASE_URL: postgresql+asyncpg://user:password@your-db-host:5432/scrob
TMDB covers the great majority of titles. A TheTVDB key is optional and only affects TVDB-specific paths:
With no key configured, all of the above fall back to TMDB-only behaviour; nothing else is affected.
Which credential to get. TheTVDB v4 has two key types:
| Type | Where | Auth | Notes |
|---|---|---|---|
| Free project key | thetvdb.com/api-information | API key only | Free for projects under the revenue threshold in TheTVDB's terms. Requires attribution — Scrob displays a "Metadata provided by TheTVDB" link on every TVDB-sourced page and in About. |
| Subscriber-supported key | TheTVDB dashboard | API key + subscriber PIN | Enter both the key and the PIN in Scrob. |
Configure it per-user in Settings → General → TVDB API Key (with the Subscriber PIN field for a subscriber key), or server-wide in Admin → Settings → TVDB as a fallback for all users. Use Test key to verify the pair before saving.
Rotation / revocation. Scrob caches the TheTVDB login token in memory for up to 29 days per key. After changing or removing the key or PIN in settings, the new credential takes effect on the next lookup; a stale token for the old credential is discarded on restart.
To enable RatingPosterDB posters, enter your RPDB API key in Settings → General → RPDB API Key, test it, and save. This is a personal display preference: it applies only to your browsing, including when viewing another user's public profile or list. Clear the key and save to return to the original artwork.
Choose poster styles and rating sources in the RPDB manager; available customizations depend on your RPDB plan. Scrob uses RPDB's account-default posters and falls back to the original artwork when an RPDB image cannot load. Movie and show portraits, including parent-show portraits in history and Next Up, can use RPDB. Episode stills, season-specific artwork, backdrops, people and collection artwork stay unchanged.
Rating posters are fetched server-side through the same image proxy as TMDB and TheTVDB artwork, so your key stays on the server and is never placed in the page. It is not written into shared media metadata, and data exports include it only when you explicitly select API Keys. Existing metadata and the server's image cache are unchanged.
Scrob supports pull synchronization from ARVIO Cloud (https://auth.arvio.tv/.netlify/functions), importing watched movies, watched episodes, and continue watching playback progress per profile.
| Variable | Default | Description |
|---|---|---|
ARVIO_APP_ANON_KEY | (Official embedded key) | Public anon API key for auth.arvio.tv. The official key is embedded by default. |
The GitHub Actions workflow (.github/workflows/docker-x64.yml) automatically validates the embedded ARVIO_APP_ANON_KEY against auth.arvio.tv during every container build, ensuring builds fail immediately with a GitHub workflow error if the public key is ever rotated.
Scrob connects to the Nuvio public Cloud API at https://api.nuvio.tv by default and also supports self-hosted Nuvio backends. A TMDB Read Access Token must be configured in Scrob so Nuvio content identifiers can be matched to movies and shows.
Scrob exchanges the email and password for a refresh token. The password is never persisted. Refresh-token rotation is handled automatically during connection checks and synchronization.
Each connection targets one Nuvio profile. Add another connection if you need to synchronize another profile from the same account.
Set NUVIO_APP_ANON_KEY to the anon/public key for your self-hosted Nuvio Supabase project, then enter that project's URL in the editable Cloud API URL field when adding the connection. If the variable is unset, Scrob continues to use the official Nuvio publishable key.
| Direction | Setting | Behavior |
|---|---|---|
| Nuvio → Scrob | Collection status | Imports the profile's library movies and series. |
| Nuvio → Scrob | Watched status | Imports watched movies and episodes with their latest watch timestamps. |
| Nuvio → Scrob | Playback progress | Imports position and duration into Continue Watching. |
| Scrob → Nuvio | Collection status | Adds or removes library membership while preserving unrelated Nuvio items. |
| Scrob → Nuvio | Watched status | Pushes watched and unwatched changes made in Scrob or imported from another connected provider. |
| Scrob → Nuvio | Playback progress | Pushes current playback positions into Nuvio's Continue Watching state as non-destructive upserts. |
Sync now runs an inbound synchronization using the enabled Nuvio → Scrob settings. Push sends the enabled collection, watched-history, and playback-progress data from Scrob to Nuvio. Pushes use merge semantics and preserve unrelated remote items.
Ratings are not synchronized with Nuvio.
Auto Pull and Auto Push can run independently every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours. Nuvio synchronization is polling-based; Nuvio does not use the media-server webhook URLs documented below.
Inbound Nuvio identifiers are normalized to TMDB for Scrob's internal matching. Before an outbound push, Scrob resolves those TMDB identifiers to Nuvio-compatible bare IMDb identifiers (tt...) and caches the mapping. Unsupported identifiers are skipped rather than attached to the wrong title.
Trakt now requires a Trakt VIP subscription to create a new API application (the client ID/secret used below) - a restriction Trakt introduced on their end, not a Scrob limitation. There are two ways to get your Trakt data into Scrob depending on whether you have VIP:
| Requires VIP | Imports | Pushes Scrob → Trakt | |
|---|---|---|---|
| OAuth connection | Yes (to create the API app) | Watched history, ratings, lists - kept in sync automatically | Yes - watched status, ratings, collection, lists, live "now watching" |
| Export import | No | Watched history, ratings (including per-episode), lists - one-time snapshot per upload | No - pull only |
Re-uploading a newer export is safe to do any time you want to catch up on new activity - imported watch plays and ratings are deduplicated, so nothing is imported twice.
Auto Pull and Auto Push apply only to the OAuth connection and can run independently every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours.
Scrob can import a one-time CSV export from Yamtrack or its fork Floppy.
.csv file on the upload box.This is a pull-only, one-shot import, like the Trakt export path above - there's no ongoing sync or connection left behind afterward. Re-uploading a newer export is safe; imported items are deduplicated. Requires a TMDB Read Access Token configured in Scrob.
Scrob uses Stremio's account datastore API at https://api.strem.io, the official Link flow at https://link.stremio.com, and Cinemeta episode metadata. Configure a TMDB Read Access Token in Scrob before synchronizing so Stremio IMDb identifiers can be mapped to Scrob media.
Scrob never asks for or stores your Stremio password. The Link flow returns an account authorization key, which is stored server-side and redacted from frontend API responses. Deleting the connection logs out that Stremio session. Each Scrob user can have one Stremio connection.
Authorization links expire in the Scrob interface after 10 minutes. Select Connect Stremio again to generate a fresh code.
| Direction | Setting | Behavior |
|---|---|---|
| Stremio → Scrob | Collection status | Imports active Stremio library movies and series. |
| Stremio → Scrob | Watched status | Imports watched movies and episodes. Series episode state is decoded from Stremio's watched bitfield using Cinemeta episode order. |
| Stremio → Scrob | Playback progress | Imports the current movie or episode position and duration into Continue Watching. |
| Scrob → Stremio | Collection status | Adds local collection items and removes only items previously pushed by this Scrob connection. Items created directly in Stremio are preserved. |
| Scrob → Stremio | Watched status | Merges movie and episode watched state into the existing Stremio record. |
| Scrob → Stremio | Playback progress | Merges the current playback position, duration, and episode identifier into Stremio. |
Sync now performs an inbound pull. The first pull reads the complete Stremio library; later pulls use Stremio modification metadata with a five-minute overlap window. Push sends the complete set of enabled outbound data. Changes imported from another provider are also forwarded to Stremio when the corresponding outbound option is enabled.
Outbound writes first fetch the current Stremio record and preserve unknown fields, addon metadata, and unrelated remote items. No-op records are skipped. Ratings and Stremio addons are not synchronized.
Auto Pull and Auto Push use separate schedules and can run every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours.
Use Full resync when the incremental cursor must be rebuilt. It reads the complete Stremio library and reconciles only collection sources owned by that Stremio connection; collection entries still backed by Jellyfin, Plex, Emby, Nuvio, or another source remain in Scrob.
Stremio exposes a current watched state rather than Scrob's complete per-play history. For series, Stremio stores a watched-episode bitfield and one lastWatched timestamp for the item, so repeated episode plays and their individual timestamps cannot be reconstructed exactly. Playback progress represents one current movie or episode per library item.
Simkl uses PIN-based authentication - no client secret is needed.
| Direction | Setting | Behavior |
|---|---|---|
| Simkl → Scrob | Watched history | Imports watched movies and episodes. |
| Simkl → Scrob | Ratings | Imports ratings. |
| Simkl → Scrob | Lists / Watchlist | Imports "plan to watch" items into a managed Simkl - Watchlist list. |
| Scrob → Simkl | Watched status | Pushes watched and unwatched changes made in Scrob or imported from another connected provider. |
| Scrob → Simkl | Ratings | Pushes rating changes. |
| Scrob → Simkl | Live scrobbling | Pushes playback start/stop events from webhooks and manual scrobble sessions in real time. |
The manual Push action sends the complete enabled watched-history and ratings snapshot, in batches of 50 items per request. Collection membership and the Simkl watchlist are not pushed back to Simkl.
Auto Pull and Auto Push can run independently every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours.
The manual Push action sends the complete enabled watched, ratings, or managed-watchlist snapshot. MDBList pagination follows next_cursor and requests the documented maximum of 1,000 items per page.
Auto Pull and Auto Push can run independently every 15 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 24 hours, or 48 hours.
Webhooks update your watch history and Continue Watching in real time. Each user's webhook URL is shown in Connections next to the relevant integration.
# Jellyfin, Plex, Emby - connection_id is shown in Connections next to each server
https://your-scrob-url/api/proxy/webhooks/{jellyfin|plex|emby}/{connection_id}?api_key=YOUR_API_KEY
# Kodi - no connection, just the API key
https://your-scrob-url/api/proxy/webhooks/kodi?api_key=YOUR_API_KEY
Playback Start, Playback Progress, Playback Stop, User Data Saved (this is what fires when you manually mark something watched/unwatched - the plugin has no separate "Mark Played" event), Item Added, and Item Deleted (keeps your Scrob collection in sync with your library without waiting for playback or a full resync).Movies and Episodes.Do not use a custom template - Jellyfin's template engine produces invalid JSON. "Send all properties" sends a well-formed payload that Scrob parses correctly.
Plex webhooks require a Plex Pass subscription.
Playback Start, Playback Stop, Item Added, Item Deleted.Emby's webhook plugin has no separate "playback progress" event, so the Now Playing bar's live progress instead comes from Scrob polling Emby's own Sessions API in the background - no extra configuration needed.
Kodi scrobbling uses the scrob-kodi add-on - no manual webhook configuration needed.
your-scrob-url/link in a browser, sign in, and approve. This works with 2FA accounts, never exposes your password, and the device can be revoked on its own from Connections → Connected Apps.The POST /api/proxy/webhooks/kodi endpoint (and the kodi/history, kodi/ratings, kodi/rating helpers the add-on uses for library sync) accept either an Authorization: Bearer device token or the ?api_key= query parameter.
Scrob supports any OpenID Connect provider (Authelia, Authentik, Keycloak, Google, etc.).
OIDC_ENABLED: "true"
OIDC_PROVIDER_NAME: "Authelia"
OIDC_CLIENT_ID: "scrob"
OIDC_CLIENT_SECRET: "your-secret"
OIDC_AUTH_URL: "https://auth.yourdomain.com/api/oidc/authorization"
OIDC_TOKEN_URL: "https://auth.yourdomain.com/api/oidc/token"
OIDC_USERINFO_URL: "https://auth.yourdomain.com/api/oidc/userinfo"
OIDC_REDIRECT_URL: "https://scrob.yourdomain.com/oidc-callback"
# OIDC_LOGOUT_URL: "https://auth.yourdomain.com/api/oidc/logout" # your provider's logout endpoint
# OIDC_SCOPES: "openid email profile" # default shown - override only if your provider needs different scopes
# OIDC_IDENTIFIER_FIELD: "email" # userinfo field used to match/create the Scrob account - default shown
OIDC_AUTO_CREATE_USERS: "true"
# OIDC_DISABLE_PASSWORD_LOGIN: "true" # uncomment to enforce SSO-only
Register Scrob as a client in your provider with redirect URI: https://scrob.yourdomain.com/oidc-callback
Scrob can require new users to verify their email address before logging in. Providing SMTP settings also enables the forgot password link on the login page.
REQUIRE_EMAIL_VALIDATION: "true"
SERVER_URL: "https://scrob.yourdomain.com"
SMTP_ADDRESS: "smtp.gmail.com"
SMTP_PORT: "587"
SMTP_ENCRYPTION: "tls"
SMTP_USERNAME: "myemail@gmail.com"
SMTP_PASSWORD: "your-app-password"
FROM_EMAIL: "myemail@gmail.com"
| Variable | Default | Description |
|---|---|---|
REQUIRE_EMAIL_VALIDATION | false | Require new users to verify their email before logging in. |
SERVER_URL | - | Public URL of your Scrob instance, used to build the validation link in emails. |
SMTP_ADDRESS | - | SMTP server hostname. |
SMTP_PORT | 587 | SMTP server port. |
SMTP_ENCRYPTION | tls | Encryption method - tls or ssl. |
SMTP_USERNAME | - | SMTP login username. |
SMTP_PASSWORD | - | SMTP login password (use an app password if using Gmail). |
FROM_EMAIL | - | Address emails are sent from. |
Contributions are welcome - whether it's a bug report, a feature request, or a pull request.
/api/proxy/.Commit messages follow Conventional Commits - feat:, fix:, chore: - as releases and changelogs are generated automatically from them.
git clone https://github.com/ellite/scrob.git
cd scrob
# Start a local database
docker compose -f docker-compose-test-db.yaml up -d
# Copy and fill in the environment file
cp .env.example .env
# Edit .env - set POSTGRES_* and SECRET_KEY at minimum
cd backend
uv sync
uv run alembic upgrade head
uv run uvicorn main:app --reload --port 7331
cd frontend
npm install
npm run dev
The frontend dev server starts on http://localhost:4321 and proxies API calls to the backend on 7331.
Scrob is licensed under the GNU General Public License v3.0.
You are free to use, modify, and distribute Scrob, provided that any derivative works are also released under the GPLv3.
Python
64.4%
Astro
33.0%
TypeScript
2.0%