A zero-dependency, self-hosted REST API for encrypted notes β
plus a tool to rescue your data from protectedtext.com.
Features Β· Quick Start Β· Export Tool Β· API Β· Docs Β· Contributing
ProtectedText is brilliant β encrypted notes, no account, just a URL and password.
This project gives you the same idea on your own server, and a CLI tool to export everything you already have on protectedtext.com to plain files.
The server never sees your plaintext. Ever.
| π Zero plaintext storage | Ciphertext, IV, and salt only β the key never leaves the client |
| π¦ Zero npm dependencies | Pure Node.js 20 built-ins, nothing to npm install |
| π Scrypt-hashed auth tokens | Client-derived tokens; the server stores only a salted hash |
| β‘ Optimistic concurrency | Version-gated writes prevent silent overwrites |
| π‘οΈ IP rate limiting | In-memory sliding window, configurable per deployment |
| π€ protectedtext.com exporter | Reverse-engineered Argon2id + AES decryption for all site types |
| π§ͺ Fully tested | 5 automated tests, pure Node.js built-in test runner |
Requirements: Node.js β₯ 20 β no other dependencies.
git clone https://github.com/laikhtman/ProtectedText_API.git
cd ProtectedText_API
npm startβ
Listening on http://127.0.0.1:3000
That's it. No npm install. No Docker required.
npm testβ creates a new site
β rejects wrong auth token
β rejects version mismatch
β rate limiter blocks over-limit requests
β rate limiter resets after window
βΆ 5 tests passed (373ms)
Pull every tab from any protectedtext.com site and save each one as a .txt file β one command, no browser needed.
Requirements: Python β₯ 3.10. Dependencies are installed automatically on first run.
python export_site.py mysiteSite ID: mysite
Password: β’β’β’β’β’β’β’β’
π Decrypting... β 33 tabs decrypted (legacy AES)
π Saved to mysite/
βββ Shopping list.txt
βββ Project ideas.txt
βββ Work notes.txt
βββ ... 30 more
python export_site.py --import sites.csvCSV format β no header row required:
siteId,password[,masterDirectory]| Column | Required | Description |
|---|---|---|
1 β siteId |
β | The protectedtext.com site identifier |
2 β password |
β | The site password |
3 β masterDirectory |
optional | Parent folder for output. Files go to <masterDir>/<siteId>/ instead of ./<siteId>/ |
Example sites.csv:
mynotes,secret123
worknotes,pass456,backup
family,pa$$w0rd,D:\exportsRunning --import sites.csv on the above would create:
backup\worknotes\*.txt
D:\exports\family\*.txt
mynotes\*.txt
Each site is fetched independently β a failure on one site is reported and the batch continues.
A folder named after the site ID is created in your working directory.
Each tab becomes a .txt file named after the tab's first line (the title).
β Supports both legacy (AES + plain password) and modern (Argon2id-chain, up to 10 iterations) protectedtext.com encryption.
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Server health check |
GET |
/api/v1/sites/:siteId |
Fetch an encrypted note |
PUT |
/api/v1/sites/:siteId |
Create or update an encrypted note |
DELETE |
/api/v1/sites/:siteId |
Delete a note |
GET /api/v1/sites/:siteId
GET /api/v1/sites/my-note{
"siteId": "my-note",
"version": 1,
"createdAt": "2026-04-24T08:00:00.000Z",
"updatedAt": "2026-04-24T08:00:00.000Z",
"ciphertext": "base64...",
"iv": "base64...",
"salt": "base64...",
"algorithm": "aes-256-gcm",
"kdf": "argon2id",
"noteHash": "sha256..."
}PUT /api/v1/sites/:siteId β create or update
PUT /api/v1/sites/my-note
Content-Type: application/json{
"ciphertext": "base64...",
"iv": "base64...",
"salt": "base64...",
"algorithm": "aes-256-gcm",
"kdf": "argon2id",
"authToken": "client-derived-secret",
"expectedVersion": 0,
"noteHash": "sha256..."
}expectedVersion: 0β create a new siteexpectedVersion: Nβ update; returns409 Conflicton mismatch
DELETE /api/v1/sites/:siteId
DELETE /api/v1/sites/my-note
Content-Type: application/json{
"authToken": "client-derived-secret",
"expectedVersion": 1
}All configuration is via environment variables:
| Variable | Default | Description |
|---|---|---|
HOST |
127.0.0.1 |
Bind address |
PORT |
3000 |
Port |
DATA_FILE |
data/sites.json |
Persistent storage path |
RATE_LIMIT_WINDOW_MS |
60000 |
Rate limit window (ms) |
RATE_LIMIT_MAX_REQUESTS |
60 |
Max requests per window per IP |
PORT=8080 DATA_FILE=/var/data/sites.json npm start- Client-side encryption only β the server derives nothing from the password
- Auth tokens β clients derive a token from their secret; server stores only a
scrypthash - No accounts β notes are addressed by
siteId, not user identity - Timing-safe equality β auth comparison uses
crypto.timingSafeEqual - Optimistic concurrency β version field prevents blind overwrites
See docs/security.md for a full breakdown and production hardening checklist.
- OpenAPI 3 spec + Swagger UI
- SQLite / PostgreSQL storage adapter
- Docker + docker-compose
- Browser client reference implementation
- Distributed rate limiting (Redis)
- Structured logging
Have an idea? Open an issue or submit a PR!
Contributions, issues, and feature requests are welcome!
See CONTRIBUTING.md to get started.
- Fork the repo
- Create a feature branch β
git checkout -b feature/amazing-feature - Commit your changes
- Push to your fork and open a PR
MIT Β© laikhtman β see LICENSE for details.
If this project helped you, please consider giving it a β β it helps others find it!