Appearance
Database location
All subscription data is stored in a single SQLite database file at:
~/.config/subtrack/subtrack.dbThe database is created automatically on first use. No database server or configuration is required.
Database structure
Seven tables — three for subscriptions with a many-to-many relationship, one for LLM API usage tracking, one for free trials, one for price change history, and one for the audit log:
subscriptions
├── id INTEGER PRIMARY KEY AUTOINCREMENT
├── name TEXT NOT NULL
├── price INTEGER NOT NULL
├── currency TEXT NOT NULL
├── cycle TEXT NOT NULL
├── status TEXT NOT NULL DEFAULT 'active'
├── billing_day INTEGER
├── created_at TEXT NOT NULL DEFAULT (date('now'))
├── notes TEXT
└── payment_method TEXT
tags
├── id INTEGER PRIMARY KEY AUTOINCREMENT
└── name TEXT NOT NULL UNIQUE
subscription_tags
├── subscription_id INTEGER NOT NULL (FK → subscriptions.id)
└── tag_id INTEGER NOT NULL (FK → tags.id)
│ PRIMARY KEY (subscription_id, tag_id)
llm_usage
├── id INTEGER PRIMARY KEY AUTOINCREMENT
├── provider TEXT NOT NULL
├── model TEXT NOT NULL
├── input_tokens INTEGER NOT NULL DEFAULT 0
├── output_tokens INTEGER NOT NULL DEFAULT 0
├── cost REAL NOT NULL
├── date TEXT NOT NULL
├── description TEXT
└── generation_id TEXT (nullable, unique index for dedup)
trials
├── id INTEGER PRIMARY KEY AUTOINCREMENT
├── name TEXT NOT NULL
├── expires_at TEXT NOT NULL
├── price INTEGER
├── currency TEXT
├── cycle TEXT
├── notes TEXT
└── created_at TEXT NOT NULL DEFAULT (date('now'))
price_history
├── id INTEGER PRIMARY KEY AUTOINCREMENT
├── subscription_id INTEGER NOT NULL (FK → subscriptions.id)
├── old_price INTEGER
├── new_price INTEGER NOT NULL
├── old_currency TEXT
├── new_currency TEXT NOT NULL
└── changed_at TEXT NOT NULL DEFAULT (datetime('now'))
audit_log
├── id INTEGER PRIMARY KEY AUTOINCREMENT
├── action TEXT NOT NULL
├── target_type TEXT
├── target_id INTEGER
├── details TEXT
├── metadata TEXT (JSON)
└── created_at TEXT NOT NULL DEFAULT (datetime('now'))Status
Each subscription has a status field: active, paused, cancelled, or archived. Only active and paused subscriptions are included in payment calculations and upcoming bills. Archived subscriptions are preserved for long-term record-keeping but excluded from totals. Cancelled subscriptions are also excluded.
Billing day
The billing_day column stores the day of month (1–31) when the subscription is billed. If not set, the creation date is used as the anchor for billing calculations. This is particularly relevant for the upcoming command, which predicts when your next bill is due.
Notes & Payment method
The notes column stores free-form text notes for a subscription (e.g. "family plan", "shared with friends"). The payment_method column records how the subscription is paid (e.g. credit_card, paypal, debit_card).
Deletion behavior
Deleting a subscription automatically removes its tag associations via ON DELETE CASCADE. Orphaned tags (with no subscriptions) can be cleaned up with subtrack tag prune.
Price history
Every time a subscription's price or currency is changed via subtrack edit, the previous values are recorded in the price_history table. View this history with subtrack history <id> or subtrack history --all.
Audit log
All mutating operations (add, edit, delete, clone, archive, import, bulk operations, tag changes, config changes, usage changes) are recorded in the audit_log table. View the audit trail with subtrack audit list and prune old entries with subtrack audit prune.
Prices are stored as integers
Prices are stored as whole numbers (integers) in the database. This avoids floating-point precision issues. For display, prices are formatted with the appropriate currency symbol and decimal places using Intl.NumberFormat.
Encryption
subtrack automatically encrypts the database file on disk using AES-256-GCM. The encryption key is either:
- Auto-generated and stored at
~/.config/subtrack/.key(on first run) - Derived from
SUBSC_CLI_DB_PASSPHRASEenvironment variable via scrypt
This means your subscription data is encrypted at rest. Backups can also be encrypted with subtrack backup --encrypt.
Key integrity verification
subtrack maintains a SHA-256 integrity hash for the encryption key file at ~/.config/subtrack/.key.sha256. On every startup, the key's SHA-256 hash is compared against the stored hash:
- Hash matches — key is intact, operation proceeds normally
- No sidecar file (first run or migration) — sidecar is created automatically
- Hash mismatch — an error is shown and the operation is aborted
This detects file corruption and tampering before any encryption/decryption operation. If the integrity check fails, restore the .key file from a backup, or delete it to generate a new key (note: this will make encrypted backups unrecoverable).
⚠️ Back up your key file or remember your passphrase. Without it, the database and encrypted backups cannot be recovered. Consider backing up both
.keyand.key.sha256together.
Backup
Use the backup command to create a timestamped copy of your database:
bash
# Backup to the default directory (~/.config/subtrack/backups/)
subtrack backup
# Creates: ~/.config/subtrack/backups/subtrack_20260617_143000.db.gz
# Backup to a custom directory
subtrack backup ~/backups
# Creates: ~/backups/subtrack_20260617_143000.db.gz
# Encrypted backup
subtrack backup --encrypt
# Creates: ~/.config/subtrack/backups/subtrack_20260617_143000.db.encIf no destination is specified, backups are saved to ~/.config/subtrack/backups/ (created automatically). Backups use exclusive file creation, so they will never overwrite an existing file. Each backup has a SHA-256 hash sidecar (<backup>.sha256) for integrity verification. See the Commands reference for full details.
Restore from backup
Use the restore command to restore from a backup. Without arguments, it interactively lists available backups:
bash
# Interactive: select from available backups
subtrack restore
# Restore from a specific file
subtrack restore ~/backups/subtrack_20260617_143000.db.gzBefore restoring, the current database is automatically backed up as a safety measure. You can also restore manually:
bash
# Stop subtrack (close all running instances)
cp ~/backups/subtrack_20260617_143000.db.gz ~/.config/subtrack/subtrack.db.gz
gunzip -k ~/.config/subtrack/subtrack.db.gzMake sure subtrack is not running when you restore, as changes are written to the database file on every command.
Custom database directory
Override the default ~/.config/subtrack directory with the SUBSC_CLI_DB_DIR environment variable:
bash
SUBSC_CLI_DB_DIR=/path/to/custom/dir subtrack listSee Configuration for more details.
