Use your Simkl custom lists as import lists in Sonarr and Radarr. Add a show, movie or anime to a list on simkl.com, and it lands in your *arr library automatically, within minutes rather than the usual 6 to 12 hours.
simkl-bridge is a small self-hosted Docker service. It reads your Simkl custom lists through Simkl's custom-list API and serves them in the Custom List format Sonarr and Radarr already import. It can also tell Sonarr and Radarr to sync the moment a list changes.
- Any custom list: all of yours (public, unlisted or private), and anyone else's public or unlisted list; TV, movies or anime.
- Fast syncs: new titles reach Sonarr/Radarr in about 3 minutes, not 6–12 hours.
- Anime aware: series, OVAs and specials go to Sonarr; anime films go to Radarr.
- Safe: read-only against Simkl. It never serves an empty or partial list, so it can't make your *arr drop titles.
- Tiny: Python standard library only, one non-root container (amd64 and arm64), no database.
- Tested end to end: every change runs against real Sonarr and Radarr containers, plus an hour-long fault-injection soak every night. See Testing.
Contents: Why · How it works · Setup guide · Add a list · Common layouts · Fast syncs · Configuration · Troubleshooting · FAQ
Simkl lets PRO and VIP members build custom lists: "Anime to watch this season", "Comfort rewatches", a friend's recommendations, and so on. Getting those into Sonarr or Radarr isn't possible out of the box:
| Sonarr / Radarr today | With simkl-bridge | |
|---|---|---|
| Simkl status lists (Watching, Plan to Watch, …) | ✅ Built-in Simkl User list | ✅ unchanged |
| Simkl custom lists | ❌ No option for them | ✅ Any list by id |
| Simkl login for custom lists (OAuth Bearer token) | ❌ The generic Custom List import can only fetch a plain URL | ✅ The bridge holds and renews the token |
| IDs the apps need (TVDB for Sonarr, TMDb for Radarr) | ❌ Not a format they can read from Simkl | ✅ Mapped per title |
| How often a list is checked | Sonarr 6 h, Radarr 12 h (hardcoded) | ✅ ~3 minutes after a change |
Simkl only opened custom lists to its API recently (beta, read-only, PRO/VIP accounts). Radarr's request for custom-list support was closed in 2024 because no API existed then. Until the *arrs support it natively, the bridge fills the gap.
flowchart LR
subgraph simkl["simkl.com"]
L["Your custom lists"]
end
subgraph home["Your server"]
B["simkl-bridge"]
S["Sonarr<br/>Custom List"]
R["Radarr<br/>Custom Lists"]
end
L -- "OAuth token<br/>(read-only)" --> B
S -- "GET /sonarr/<list id>" --> B
R -- "GET /radarr/<list id>" --> B
B -. "list changed:<br/>sync now" .-> S
B -. "list changed:<br/>sync now" .-> R
- Sonarr and Radarr pull. Each list you add in Sonarr/Radarr is a
Custom List pointing at the bridge:
http://simkl-bridge:8080/sonarr/<id>or/radarr/<id>. When the app asks, the bridge reads that list from Simkl and answers with each title's TVDB id (Sonarr) or TMDb id (Radarr). - The bridge pushes a nudge (optional). Given each app's API key, the bridge watches Simkl. When a list changes, it tells exactly the Sonarr/Radarr lists using it to sync now. The apps then pull as in step 1, just much sooner. Details below.
The bridge stores nothing but its access token and a cache of id mappings. Sonarr and Radarr stay in charge of what gets added and how.
There are two parts:
- Part 1: one-time setup (about 15 minutes). A Simkl app, a sign-in, and the container.
- Part 2: add a list (about 2 minutes, per list). Repeat it for every Simkl list you want in Sonarr or Radarr.
| You need | Notes |
|---|---|
| A Simkl PRO or VIP account | Simkl only offers custom lists through its API on these plans |
| Docker with Compose | The bridge runs as one small container |
| Sonarr v4 and/or Radarr | Tested with Sonarr 4.0.20 and Radarr 6.4.4, stable and develop builds |
| The Docker network Sonarr/Radarr are on | The bridge must join it. To find it: docker inspect sonarr --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' |
- Sign in at simkl.com and open Settings → Developer.
- Choose Add / create a new app, and pick AUTH V2.
- App type: TV, devices & command line.
⚠️ Pick exactly this one. It can't be changed later, and the other types (mobile/desktop, server) use a sign-in the bridge doesn't support. - Name: anything. Simkl asks that names mentioning Simkl read "… for Simkl", e.g. List Bridge for Simkl. Leave the optional fields empty.
- Create it and copy the Client ID (a 64-character code). It's public; this app type has no secret.
Run this in the folder where you'll keep the bridge's files, replacing
<client_id>:
docker run --rm -it -e SIMKL_CLIENT_ID=<client_id> \
-e XDG_RUNTIME_DIR=/out -v "$PWD:/out" --user "$(id -u)" \
ghcr.io/jad0083/simkl-bridge:latest auth- It prints a link like
https://simkl.com/pin?user_code=ABCD-EFGH. Open it, signed in to your PRO/VIP account, and approve. The bridge asks only for read access. - It saves the refresh token to
./simkl-refresh-token.secret. The token is never shown on screen; you see its length and a fingerprint, so you can confirm it later without revealing it. - Copy the file's contents into your
.env(Step 4), then delete the file.
You only do this once. The refresh token lasts 180 days and renews itself every time the bridge uses it.
This is optional but recommended: it makes new titles arrive in about 3 minutes instead of 6–12 hours (see How fast syncs work).
In each app: Settings → General → Security → API Key. Copy it. Skip this step to run the bridge pull-only.
Next to your compose.yaml:
SIMKL_CLIENT_ID=<the client ID from step 1>
SIMKL_REFRESH_TOKEN=<the contents of simkl-refresh-token.secret>
# Fast syncs (step 3). Leave both lines of an app out to skip it.
SONARR_API_KEY=<Sonarr's API key>
RADARR_API_KEY=<Radarr's API key>Keep .env private (chmod 600 .env). The bridge never logs these values.
If Sonarr and Radarr are in the same compose.yaml, add this service to
it. Services in one Compose project share a network by default, so there's
nothing else to set:
simkl-bridge:
image: ghcr.io/jad0083/simkl-bridge:latest
container_name: simkl-bridge
restart: unless-stopped
environment:
- SIMKL_CLIENT_ID=${SIMKL_CLIENT_ID}
- SIMKL_REFRESH_TOKEN=${SIMKL_REFRESH_TOKEN}
- SONARR_URL=http://sonarr:8989 # your Sonarr's container name and port
- SONARR_API_KEY=${SONARR_API_KEY}
- RADARR_URL=http://radarr:7878 # your Radarr's container name and port
- RADARR_API_KEY=${RADARR_API_KEY}
volumes:
- ./simkl-bridge:/dataIf they run elsewhere, give the bridge its own compose.yaml and join
their network (the name from Before you start):
services:
simkl-bridge:
# ...same as above...
networks: [media]
networks:
media:
external: true
name: media # replace with your network's nameThings to check:
SONARR_URL/RADARR_URLuse the container name and internal port, not a published port or a public domain. If Sonarr is served under a URL base (e.g./sonarr), include it:http://sonarr:8989/sonarr.- The data folder must be writable by the container's user (uid 10001):
mkdir -p simkl-bridge && sudo chown 10001:10001 simkl-bridge. - No port is published. Sonarr and Radarr reach the bridge by name. It has no login of its own, so don't expose it to the internet.
docker compose up -d simkl-bridge
docker logs simkl-bridgeYou should see:
watching for list changes every 180s; syncs go to sonarr, radarr
simkl-bridge listening on :8080
The first line only appears with fast syncs on. Then check that Sonarr and Radarr can reach the bridge:
docker exec sonarr curl -s http://simkl-bridge:8080/healthz # {"status": "ok"}
docker exec radarr curl -s http://simkl-bridge:8080/healthzSetup is done. Now add lists.
Repeat these steps for each Simkl list.
- On simkl.com, open your profile's Lists section and create a new list.
- Choose its type (TV, Movies or Anime). A Simkl list holds one type, and
the type decides which app it can feed (step 3).
- Anime films go in an Anime list, not a Movies list: Simkl files them as anime.
- An Anime list can mix series and films. The bridge splits it for you.
- Add titles from any title's page with Add to list.
Privacy: the bridge reads lists as you (the account you signed in with in Part 1), so:
| List | Works? |
|---|---|
| Your own list: public, unlisted or private | ✅ |
| Someone else's public or unlisted list | ✅ (use its ID) |
| Someone else's private list that you collaborate on | ✅ |
| Someone else's private list you're not on | ❌ Test reports "the list is private" |
Open the list on simkl.com. The number in the page address is the list
ID, e.g. 152642.
| Simkl list type | Add it to | List URL |
|---|---|---|
| TV | Sonarr | http://simkl-bridge:8080/sonarr/<list id> |
| Movies | Radarr | http://simkl-bridge:8080/radarr/<list id> |
| Anime, series only | Sonarr | http://simkl-bridge:8080/sonarr/<list id> |
| Anime, films only | Radarr | http://simkl-bridge:8080/radarr/<list id> |
| Anime, series and films | both, with the same list ID | both URLs above |
How anime is split: each anime title has a subtype on Simkl. Movie goes to Radarr; TV, OVA, ONA and special go to Sonarr. A title only ever reaches one app. An anime list with no films yet is empty as far as Radarr is concerned; see the next note.
Empty lists: Sonarr accepts an empty list without complaint. Radarr shows the warning "No results were returned from your import list" when you test or save one. It's only a warning, so the list can still be saved, and films you add later arrive automatically. Or add one film first, and it saves without a warning.
Settings → Import Lists → + (Add List), then under Advanced choose Custom List:
| Field | What to set |
|---|---|
| Name | Anything, e.g. Simkl – Anime |
| Enable Automatic Add | ✅ On, or titles are only listed, never added |
| Monitor | Your preference (e.g. All Episodes) |
| Monitor New Items | Your preference |
| Root Folder | Where these series belong (e.g. your anime folder for an anime list) |
| Quality Profile | Your preference |
| Series Type | Anime for an anime list; Standard otherwise |
| Season Folder | Your preference |
| Search for Missing Episodes | On, to start downloading when a series is added |
| List URL | http://simkl-bridge:8080/sonarr/<list id> |
Settings → Lists → + (Add List), then under Advanced choose Custom Lists:
| Field | What to set |
|---|---|
| Name | Anything, e.g. Simkl – Anime films |
| Enable and Enable Automatic Add | ✅ Both on |
| Monitor | Usually Movie Only |
| Minimum Availability | Usually Released |
| Quality Profile | Your preference |
| Root Folder | Where these films belong (e.g. your anime movies folder) |
| Search on Add | On, to start downloading when a film is added |
| List URL | http://simkl-bridge:8080/radarr/<list id> |
- Press Test. It should succeed. If it doesn't, the message says why (see Troubleshooting). The usual causes are a movie list pointed at Sonarr (or the reverse), a wrong list ID, and Radarr's "No results" warning for a list that's still empty. Added a title a moment ago? Test again: from 0.3.3 an edit shows up on the next Test (after 10 seconds).
- Press Save.
- To see exactly what the app will receive:
Each title appears with its TVDB ID (Sonarr) or TMDb ID (Radarr).
docker exec sonarr curl -s http://simkl-bridge:8080/sonarr/<list id>
When titles appear: Radarr syncs a new list as soon as it's saved. Sonarr doesn't sync a list when it's added, only when it's edited, so with fast syncs on, the bridge syncs it within about 3 minutes. Without fast syncs it waits for Sonarr's own schedule. From then on the list stays in sync by itself; there's nothing to change on the bridge.
Add a title to the list on simkl.com. Within about 3 minutes the bridge logs:
watch: list 152642 changed; sonarr list #3 sync requested
Then Sonarr's own log shows Import List Sync Completed … Series added: 1.
| You want | Do this |
|---|---|
| A TV watchlist in Sonarr | TV list → Sonarr only |
| A movie watchlist in Radarr | Movies list → Radarr only |
| One anime list for everything | Anime list → both apps with the same ID. In Sonarr set Series Type: Anime and your anime root folder; in Radarr use your anime-movies root folder |
| Anime films kept separate | A second Anime list with only films → Radarr only |
| A friend's recommendations | Their public list's ID → the matching app. Changes arrive within the full-check interval (BRIDGE_FULL_CHECK, e.g. 15 min at 900) |
Out of the box, Sonarr re-checks a Custom List at most every 6 hours and Radarr every 12 hours. Those minimums are hardcoded in each app, but a sync requested for one specific list is honoured immediately. With the API keys from Step 3, the bridge makes that request for you:
sequenceDiagram
participant You as You (simkl.com)
participant Simkl
participant Bridge as simkl-bridge
participant Sonarr
You->>Simkl: add a show to a list
loop every 3 minutes
Bridge->>Simkl: anything changed? (one call)
end
Simkl-->>Bridge: yes, list 152642 changed
Bridge->>Sonarr: sync list #3 now
Sonarr->>Bridge: GET /sonarr/152642
Bridge-->>Sonarr: the list, with TVDB ids
Sonarr->>Sonarr: add the new show
| List | Typical delay | Why |
|---|---|---|
| Your own lists | ~3 minutes (18 seconds measured) | Simkl keeps one account-wide "last changed" stamp for your lists; the bridge checks it every 3 min |
| Someone else's list | up to 1 hour by default; 15 min with BRIDGE_FULL_CHECK=900 |
Their edits don't move your stamp, so a periodic full check catches them |
What it does automatically:
- Finds lists by itself. Any Sonarr Custom List or Radarr Custom Lists entry whose URL points at the bridge is watched, including ones added later. Each is synced once when first seen, and after a restart, to catch up.
- Syncs only what changed, and only in the apps using that list. Disabled lists are left alone.
- Makes sure the app really got it. A sync counts only once the app has actually fetched the list. If its fetch failed, for example on a Simkl hiccup, the bridge asks again, rather than leaving it to the app's own 6–12 hour retry.
- Costs almost nothing: one small call every 3 minutes, whatever the number of lists (about 480 a day). Simkl allows 1,000 (PRO) or 10,000 (VIP) a day per user, shared with your other Simkl apps.
Good to know:
- The stamp starts working after your first list edit. Simkl leaves the "last changed" stamp empty until you first edit a list. Until then, your own lists are caught by the full check.
- API keys are admin keys. Neither app offers a read-only key. The bridge only ever sends them to the URL you configured and never logs them. Leave them out to keep the bridge pull-only.
| Variable | Required | Default | What it does |
|---|---|---|---|
SIMKL_CLIENT_ID |
yes | Your Simkl AUTH V2 app's client ID | |
SIMKL_REFRESH_TOKEN |
yes | From the one-time auth step |
|
SONARR_URL, SONARR_API_KEY |
no | Enable fast syncs for Sonarr (set both or neither) | |
RADARR_URL, RADARR_API_KEY |
no | Enable fast syncs for Radarr (set both or neither) | |
BRIDGE_WATCH_INTERVAL |
no | 180 |
Seconds between change checks (minimum 30) |
BRIDGE_FULL_CHECK |
no | 3600 |
Seconds between full checks, which catch other people's lists; 900 is a good value if you use them |
BRIDGE_LISTS |
no | all | Comma-separated list IDs this bridge may serve |
BRIDGE_MIN_REFRESH |
no | 10 |
Seconds a list is served from cache without asking Simkl whether it changed (absorbs an app's burst of requests) |
BRIDGE_DATA_DIR |
no | /data |
Where the access token and ID cache live (mode 0600) |
BRIDGE_PORT |
no | 8080 |
Listening port |
SIMKL_CLIENT_SECRET |
no | Only for a server-type Simkl app (not recommended) | |
SIMKL_API_BASE |
no | https://api.simkl.com |
For testing against a stand-in Simkl |
Endpoints: GET /sonarr/<list id> (Sonarr Custom List JSON with
tvdbId), GET /radarr/<list id> (Radarr Custom Lists JSON with the TMDb
id), and GET /healthz.
Images (linux/amd64 and linux/arm64, e.g. a Raspberry Pi 4/5):
| Tag | What it is |
|---|---|
:latest |
The latest release |
:0.3.0, :0.3 |
A release, or the newest patch of a minor version |
:edge |
The newest build of main, which passed every check |
:<12-char commit> |
One exact build: pin this, with its digest, for reproducible deployments |
What Test in Sonarr/Radarr (or curl http://simkl-bridge:8080/sonarr/<id>) tells you:
| Result | Meaning | Fix |
|---|---|---|
404 … not found |
No list with that ID | Check the number in the list's URL |
400 … holds movies; point radarr at … |
A movie list was added to Sonarr, or a TV list to Radarr | Use the other app |
502 … not Simkl PRO or VIP |
The signed-in account isn't PRO/VIP | Custom lists need PRO/VIP |
403 … the list is private; only its owner and its collaborators can read it |
Someone else's private list, and you're not a collaborator (your own private lists always work) | Ask the owner to make it public or unlisted, or to add you as a collaborator |
502 … token refresh failed |
The refresh token was revoked or is from another app | Re-run the auth step |
403 … not in BRIDGE_LISTS |
You restricted which lists are served | Add the ID to BRIDGE_LISTS |
| Test says "No results were returned from your import list" | The list is empty on Simkl (Radarr warns about empty lists; Sonarr doesn't). Or it was until a moment ago: before 0.3.3 the bridge could serve a just-edited list from a 15-minute cache | Add a title on simkl.com and Test again; from 0.3.3 an edit shows up on the next Test (after 10 seconds). Or save anyway: it's a warning, and titles added later arrive automatically |
Log: … list #N was refused by the bridge: … |
That Sonarr/Radarr list can't be served as configured: wrong app for the list's type, an unknown list ID, someone else's private list, or not in BRIDGE_LISTS |
Fix that list's settings in the app, using the reason in the log line. The bridge stops re-requesting it until the list changes |
| A title never appears | Simkl has no TVDB (Sonarr) or TMDb (Radarr) id for it | docker logs simkl-bridge names each skipped title |
Log: … still hasn't fetched after 5 re-requests; giving up |
The app accepted the sync request but its fetch keeps failing or never arrives | Check that app's own log (System → Logs) for the error. Its fetch must come from the app itself: a proxy that rewrites its User-Agent hides it from the bridge |
Log: sonarr unreachable / radarr unreachable |
SONARR_URL / RADARR_URL is wrong, or the bridge isn't on their network |
Use the container name and internal port, plus any URL base; see Step 5 |
The bridge never answers an error with an empty list. Sonarr/Radarr simply retry later, and nothing is removed from your library.
Doesn't Sonarr already support Simkl? Only your status lists (Watching, Plan to Watch, Completed, …) via the built-in Simkl User list. Custom lists aren't supported by Sonarr or Radarr.
Will titles be removed if I take them off the Simkl list? Only if you've enabled Clean Library on the list in Sonarr/Radarr; by default nothing is removed. Either way, a Simkl outage can never look like an empty list: the bridge returns an error instead.
Does it work with a free Simkl account? No. Simkl only gives PRO and VIP accounts access to custom lists through its API.
Can I use a friend's list, or a public list I found?
Yes, if it's public or unlisted, or private with you as a collaborator. Use its
ID like any other list. Its changes arrive within your BRIDGE_FULL_CHECK
interval.
Do private lists work? Your own do, always: the bridge reads lists as you. Someone else's private list works only if they've added you as a collaborator.
How does anime work? An anime list can feed both apps. Series, OVAs, ONAs and specials go to Sonarr (set Series Type: Anime); anime films go to Radarr. Simkl often lists anime by season ("… Season 2"); Sonarr adds the whole series, which is what Sonarr expects.
Does it write anything to Simkl? No. It asks for read-only access and never writes.
Why not sync my Simkl lists to Trakt and use the Trakt lists instead? You can, but it takes another service in the middle. Trakt's free tier limits lists to 100 items, and changes still wait for the 12-hour Trakt list minimum.
- Simkl's custom-list API is in beta. The bridge follows its documented behaviour and is tested against a live account.
- Lists over 10,000 items can't be read through the API. The bridge refuses them rather than serve a truncated list.
- Sonarr 4.x needs a TVDB id, and Radarr a TMDb id. Titles Simkl can't map are skipped and logged.
Every pull request and push to main runs:
| Check | What it proves |
|---|---|
| Unit and integration tests on Python 3.11, 3.12 and 3.13 | The logic, plus the real bridge process over real HTTP against a stand-in Simkl that mimics Simkl's quirks (silent page clamping, premium_only, expiring tokens) |
| Container smoke test | The image runs as non-root, rejects bad config with a clear message, becomes healthy, works with a read-only root filesystem, and never logs a token |
| Vulnerability scan (Trivy) | No fixable HIGH or CRITICAL issues in the image |
| End to end against real Sonarr and Radarr: stable builds, develop builds, and under a URL base | Titles are actually added; a list edit reaches both apps through a targeted sync within 2 minutes; a movie list offered to Sonarr is refused with a reason; with Sonarr's list cleaning on, a Simkl outage unmonitors nothing |
| Soak: 4 minutes per change, 60 minutes nightly | Under sustained Simkl faults (429s, 503s, dropped connections, slow replies, tokens expiring every ~40 s) and constant list edits: never a partial, mixed or empty answer; every edit delivered; no memory or thread growth; no crash |
The end-to-end suite also runs weekly, so a new Sonarr or Radarr release that breaks the integration is caught even when nothing here has changed.
Tested with: Sonarr 4.0.20, Radarr 6.4.4 (and their develop builds at the time of each run).
python3 -m venv .venv && .venv/bin/pip install pytest
.venv/bin/pytest # unit + integration; no account needed
docker build -t simkl-bridge:dev .
tests/container/smoke.sh simkl-bridge:dev # container checks
PYTHON=.venv/bin/python tests/e2e/run.sh # real Sonarr/Radarr (Docker, internet)
.venv/bin/python tests/soak/soak.py --minutes 5tests/support/fake_simkl.py is a standalone stand-in for Simkl's API; point
the bridge at it with SIMKL_API_BASE. docs/design.md
explains the design decisions and why. Issues and pull requests are welcome.
Releasing: bump __version__ in src/simkl_bridge/__init__.py, merge,
then push a tag vX.Y.Z on that commit. CI verifies the tag matches, runs
everything, publishes the images and creates the GitHub Release.
Not affiliated with or endorsed by Simkl, Sonarr or Radarr. List data comes from Simkl.