A simple Python/FastAPI server for syncing reading progress across KOReader devices.
Don't want to deploy your own? Use the public sync server:
https://www.null-space.xyz/reader
Just point your KOReader or Readest app to this URL and create an account.
Warning
Kindle Sync Timeout Issue
If you can login from KOReader on Kindle but sync fails with "something went wrong when syncing progress", you may need to increase KOReader's sync timeout. This commonly happens when using serverless backends (like AWS Lambda) that have cold start delays. If more people use this service, I can make it more available to improve response times.
Symptoms:
- Login works, but push/pull progress fails/isn't working
- KOReader debug log shows:
KOSyncClient:update_progress failure: common/Spore/Protocols.lua:85: wantread
Fix:
- Connect your Kindle to your computer via USB
- Open the file:
<Kindle>/koreader/plugins/kosync.koplugin/KOSyncClient.lua - Find this line near the top (around line 6):
local PROGRESS_TIMEOUTS = { 2, 5 }
- Change it to:
local PROGRESS_TIMEOUTS = { 5, 15 }
- Save the file, eject Kindle, and restart KOReader
This increases the sync timeout from 5 seconds to 15 seconds, giving the server enough time to respond during cold starts.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn main:app --reload --port 8080docker compose up -dDeploy as a serverless Lambda function with DynamoDB storage.
- AWS CLI configured with credentials
- Terraform >= 1.0
- Python 3.12
Create the shared S3 bucket for Terraform state:
cd deployment
./bootstrap.sh- Create terraform variables file:
cp deployment/terraform.tfvars.example terraform/terraform.tfvars- Edit
terraform/terraform.tfvarswith your password salt:
password_salt = "your-secure-random-salt" # Generate with: openssl rand -hex 32- Build and deploy:
./deployment/deploy.shThis creates:
- Lambda function (
reader-progress-prod) - DynamoDB tables (
reader-progress-prod-users,reader-progress-prod-progress) - IAM role with minimal permissions
| Environment Variable | Default | Description |
|---|---|---|
DB_BACKEND |
sql |
Database backend (sql or dynamodb) |
PASSWORD_SALT |
default-salt-change-me |
Salt prepended to passwords before hashing |
DATABASE_URL |
sqlite:///./data/koreader.db |
SQLite/PostgreSQL database URL |
| Environment Variable | Description |
|---|---|
DB_BACKEND |
Set to dynamodb |
PASSWORD_SALT |
Salt for password hashing (set via Terraform) |
DYNAMODB_USERS_TABLE |
Users table name (set via Terraform) |
DYNAMODB_PROGRESS_TABLE |
Progress table name (set via Terraform) |
AWS_REGION |
AWS region (set via Terraform) |
- Open a book in KOReader
- Open the menu (swipe down from top) and tap the settings icon (wrench)
- Select Progress sync > Custom sync server > enter your server URL (e.g.,
http://your-server:8080) - Select Register or Login and enter your credentials
- Select Push progress from this device to test
- optional Enable Auto sync for automatic progress updates when opening/closing books
Since, iOS doesn't seem to have a KOReader app I could find, I went with Readest which supports KOReader progress sync.
- Open Readest
- Open a book(I used Calibre content server's OPDS library to sync book files across devices)
- Open book menu table of contents in the bottom left(bullet list icon)
- Select hamburger menu in the top right > Select KOReader Sync
- Enter enter your server URL (e.g.,
http://your-server:8080) and credentials
Each user has their own isolated account with separate reading progress data. Users authenticate via HTTP headers on every request, and progress is stored per-user, per-document.
User A (kindle) User A (phone) User B (tablet)
│ │ │
└───────┬───────────┘ │
│ │
User A's Progress User B's Progress
┌─────────────┐ ┌─────────────┐
│ book1: 25% │ │ book1: 80% │
│ book2: 50% │ │ book3: 10% │
└─────────────┘ └─────────────┘
- Device reads a book - KOReader calculates a document hash (MD5 of the file) and tracks reading position
- Device uploads progress - Sends document hash, XPath position, percentage, and device info
- Another device opens same book - Queries server using the same document hash
- Server returns latest progress - Device can jump to the synced position
The document hash ensures the same book is identified across devices regardless of filename.
| Column | Type | Description |
|---|---|---|
| id | INTEGER | Primary key |
| username | TEXT | Unique username |
| password_hash | TEXT | Bcrypt hash of salted MD5 password |
| Column | Type | Description |
|---|---|---|
| id | INTEGER | Primary key |
| user_id | INTEGER | Foreign key to users.id |
| document | TEXT | MD5 hash of the document |
| progress | TEXT | XPath position (e.g., /body/p[42]) |
| percentage | REAL | Reading progress 0.0-1.0 |
| device | TEXT | Device name (e.g., "Kindle Paperwhite") |
| device_id | TEXT | Unique device identifier |
| timestamp | INTEGER | Unix timestamp of last update |
Key constraint: One progress record per (user_id, document) pair. Updates replace existing records.
| Attribute | Type | Key |
|---|---|---|
| username | String | Partition Key |
| password_hash | String | - |
| Attribute | Type | Key |
|---|---|---|
| user_id | String | Partition Key |
| document | String | Sort Key |
| progress | String | - |
| percentage | Number | - |
| device | String | - |
| device_id | String | - |
| timestamp | Number | - |
All endpoints except /users/create and /health require authentication via headers:
x-auth-user: <username>
x-auth-key: <md5_hash_of_password>
KOReader sends passwords as MD5 hashes in all requests (both registration and authentication). The server then applies additional salting and bcrypt hashing before storage.
Password flow:
- Client computes
MD5(raw_password) - Client sends MD5 hash to server (in JSON body for registration, in
x-auth-keyheader for auth) - Server stores
bcrypt(salt + md5_hash)in database
When using curl or other clients, you must send the MD5 hash of the password, not the raw password:
# Generate MD5 hash of password
echo -n "mypass" | md5 # macOS
echo -n "mypass" | md5sum | cut -d' ' -f1 # LinuxRegister a new user account.
Note: KOReader sends the password as an MD5 hash during registration. When using curl or other clients, you must send the MD5 hash of your password, not the raw password.
# MD5 of "mypass" is a029d0df84eb5549c641e04a9ef389e5
curl -X POST http://localhost:8080/users/create \
-H "Content-Type: application/json" \
-d '{"username": "myuser", "password": "a029d0df84eb5549c641e04a9ef389e5"}'Response: {"status": "success"} (201) or {"detail": "Username already exists"} (402)
Verify credentials are valid.
# MD5 of "mypass" is a029d0df84eb5549c641e04a9ef389e5
curl http://localhost:8080/users/auth \
-H "x-auth-user: myuser" \
-H "x-auth-key: a029d0df84eb5549c641e04a9ef389e5"Response: {"status": "authenticated"} (200) or {"detail": "Unauthorized"} (401)
Update reading progress for a document.
curl -X PUT http://localhost:8080/syncs/progress \
-H "Content-Type: application/json" \
-H "x-auth-user: myuser" \
-H "x-auth-key: a029d0df84eb5549c641e04a9ef389e5" \
-d '{
"document": "0b229176d4e8db7f6d2b5a4952368d7a",
"progress": "/body/DocFragment[42]/body/p[3]/text().0",
"percentage": 0.25,
"device": "Kindle Paperwhite",
"device_id": "A1B2C3D4"
}'Response: {"status": "success"} (200)
Retrieve the latest progress for a document.
curl http://localhost:8080/syncs/progress/0b229176d4e8db7f6d2b5a4952368d7a \
-H "x-auth-user: myuser" \
-H "x-auth-key: a029d0df84eb5549c641e04a9ef389e5"Response:
{
"document": "0b229176d4e8db7f6d2b5a4952368d7a",
"progress": "/body/DocFragment[42]/body/p[3]/text().0",
"percentage": 0.25,
"device": "Kindle Paperwhite",
"device_id": "A1B2C3D4",
"timestamp": 1706123456
}Health check endpoints for monitoring.
-
calibre - calibre is a powerful and easy to use e-book manager. It’s also completely free and open source and great for both casual users and computer experts.
-
koreader/koreader - KOReader ebook reader application
-
koreader-calibre-plugin - A calibre plugin to synchronize metadata from KOReader to calibre.
-
readest - Readest is a modern, open-source ebook reader for immersive reading. Seamlessly sync your progress, notes, highlights, and library across macOS, Windows, Linux, Android, iOS, and the Web.
Other Sync server implementations:
- koreader/koreader-sync-server - Official Lua/OpenResty implementation
- nperez0111/koreader-sync - TypeScript/Bun implementation
- myelsukov/koreader-sync - Go implementation