Use at your own risk. This tool can write to or delete data from your Simkl library; PRs are welcome.
Small JBang importer that converts a TV Time Out export into Simkl /sync/history requests.
It reads the tvtime-series-<date>.json and tvtime-movies-<date>.json files from a TV Time Out zip, extracted directory, or individual JSON file. TV Time Out exports are produced by the separate TV Time Out by Refract browser extension; this importer is not affiliated with TV Time, Simkl, Refract, or that extension. Dry-run is the default so the generated payload can be inspected before anything is sent to Simkl.
Watched history, watchlist-only items, and watched special episodes are imported by default.
Install JBang using the official installation instructions.
Run the local checkout:
jbang SimklTvTimeOutImporter.java --helpJBang can also run the script directly from a GitHub repository URL:
jbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java --helpFor repeatable imports, prefer a tag or commit SHA instead of master:
jbang https://github.com/bric3/simkl-tvtimeout-importer/blob/<tag-or-commit>/SimklTvTimeOutImporter.java \
--input 2026-07-05-tvtimeout-export.zipSee JBang's remote execution documentation for URL execution and trust prompts.
Before any use, it is required to setup your SIMKL API key.
- Navigate to the developper settings : https://simkl.com/settings/developer/
- Click Get API keys
- Add a new key for a new app
- Name:
simkl-tvtimeout-importeror whatever you wish to put there - Description: whatever you want
- Redirect URI:
urn:ietf:wg:oauth:2.0:oob, this one is important to allow the PIN authentication.
- Name:
Do not put SIMKL_CLIENT_ID or SIMKL_CLIENT_SECRET literal values in the shell command. Shell history will record the command line.
The default flow is Simkl PIN login. Keep the app credentials from the Simkl developer page somewhere safe (e.g 1Password):
Prefer passing the developer credentials with op item get command substitutions. The literal secret values are not written to shell history, and the PIN URL remains plain clickable terminal output:
jbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java \
--client-id="$(op item get simkl --fields client-id --reveal)" \
--client-secret="$(op item get simkl --fields client-secret --reveal)" \
--helpThis opens a link on SIMKL where you are asked to paste a code,mwhich will give access to this program.
This keeps literal secrets out of shell history, note command substitutions expand before jbang starts. A local process running as the same user may be able to inspect process arguments while the command is running.
Alternatively, if using 1Password, there's op run --env-file .env.1password -- ... which wraps the process and keep the env var protected. It uses the op://... references in the env file, however it's draw back is that it will prevent the printed PIN URL from being linkified by the terminal. So you have to manually edit the link pasted in the browser.
If you deliberately store a user OAuth access token, set SIMKL_ACCESS_TOKEN and pass --use-access-token. Do not point SIMKL_ACCESS_TOKEN at client-secret; the client secret belongs in SIMKL_CLIENT_SECRET. The tool also accepts --use-access-token=<token>, but prefer the environment variable form because command arguments can be visible to other local processes.
Stored-token mode example:
SIMKL_ACCESS_TOKEN="$(op item get simkl --fields access-token --reveal)" \
jbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java \
--client-id="$(op item get simkl --fields client-id --reveal)" \
--client-secret="$(op item get simkl --fields client-secret --reveal)" \
--input 2026-07-05-tvtimeout-export.zip \
--use-access-token \
--applyjbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java \
--input 2026-07-05-tvtimeout-export.zip \
--output simkl-history-payload.jsonjbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java \
--client-id="$(op item get simkl --fields client-id --reveal)" \
--client-secret="$(op item get simkl --fields client-secret --reveal)" \
--input 2026-07-05-tvtimeout-export.zip \
--apply--apply posts batches to https://api.simkl.com/sync/history with skip_auto_watching=yes. The importer sends watched movies and watched TV episodes with TVDB/IMDb IDs and normalized GMT watched_at timestamps.
Save the current Simkl state before importing:
jbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java \
--client-id="$(op item get simkl --fields client-id --reveal)" \
--client-secret="$(op item get simkl --fields client-secret --reveal)" \
--backup-current simkl-backup.jsonOr make a backup immediately before a live import:
jbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java \
--client-id="$(op item get simkl --fields client-id --reveal)" \
--client-secret="$(op item get simkl --fields client-secret --reveal)" \
--input 2026-07-05-tvtimeout-export.zip \
--backup-before-apply simkl-backup-before-import.json \
--applyPreview a restore payload from a saved backup:
jbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java \
--restore simkl-backup.json \
--output simkl-restore-payload.jsonApply a restore. This deletes the current Simkl library first, then replays the backup:
jbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java \
--client-id="$(op item get simkl --fields client-id --reveal)" \
--client-secret="$(op item get simkl --fields client-secret --reveal)" \
--restore simkl-backup.json \
--backup-before-apply simkl-backup-before-restore.json \
--apply \
--confirm-restore-deleteAn empty or whitespace-only restore file is treated as an empty backup. With --apply --confirm-restore-delete, that means the current Simkl library is deleted and nothing is replayed.
To remove everything directly, preview the delete payload first:
jbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java \
--client-id="$(op item get simkl --fields client-id --reveal)" \
--client-secret="$(op item get simkl --fields client-secret --reveal)" \
--remove-everything \
--output simkl-delete-payload.jsonApply the wipe only with an explicit confirmation flag:
jbang https://github.com/bric3/simkl-tvtimeout-importer/blob/master/SimklTvTimeOutImporter.java \
--client-id="$(op item get simkl --fields client-id --reveal)" \
--client-secret="$(op item get simkl --fields client-secret --reveal)" \
--remove-everything \
--backup-before-apply simkl-backup-before-delete.json \
--apply \
--confirm-remove-everythingIf SIMKL_ACCESS_TOKEN is set, the importer still ignores it by default. Add --use-access-token when you explicitly want stored-token mode instead of PIN login.
Backups store Simkl's raw per-type all-items responses plus metadata. Restore is best-effort for the fields Simkl exposes through that API: IDs, statuses, watched episodes, watched timestamps, ratings, memos, and watchlist add dates.
Simkl sync-guide compliance:
- Backup and delete discovery reads
/sync/all-items/shows,/sync/all-items/movies, and/sync/all-items/animesequentially. - Live write batches wait at least 1.1 seconds between POST requests.
- This tool does not run a continuous sync loop or background polling.
Useful options:
--source-zone Europe/Paris: timezone for TV Time Out episode timestamps that have no offset.--watched-only: skips unwatched movies and shows.--skip-specials: skips watched season 0/special episodes. Specials are included by default.--batch-size 20: changes top-level movies/shows per API call.
Rewatch counts are reported but not imported. TV Time Out only provides counts, while Simkl rewatch history needs explicit rewatch sessions and safer timestamp handling.
This project is licensed under the Mozilla Public License 2.0. The official MPL 2.0 text is also available from Mozilla.