You subscribed to Framer $20/mo for one client pitch in January. Used it twice. It's May. You've paid $80 you never bill. Notion AI $10/mo, Loom $15/mo, Adobe $55/mo — some client-billable, some personal. Your bank statement shows 11 recurring charges, you can name 7. The other 4 are subscription zombies draining $50/mo = $600/year.
Sheets didn't warn you. 1Password stores the login, not the renewalDate. And your calendar has 3 renewal reminders — you snoozed the 14 days one and missed the 7 days one.
Why SaaS Audits Fail
No billable split. Figma $15 for Acme should be billableTo client src/lib/db/schema.ts:227 via invoices items jsonb src/lib/db/schema.ts:74; Notion $10 personal is billableTo personal overhead — missing enum = you eat cost. Cycle blindness. billingCycle monthly|yearly src/lib/db/schema.ts:224 should drive RENEWAL_MILESTONES [7,3,1,0] src/app/api/cron/daily/route.ts:17, but Sheet Monthly text drives nothing. No client link. Surfer $69 under personal without clientId src/lib/db/schema.ts:228 leaks $69 margin, $200/mo at 15 subs. Invisible until charged. Sheet 2026-09-18 does nothing at 7 days; you need daysUntil(renewalDate) + notifications renewal_due before capture.
The Linked Subscription Model That Pays For Itself
RunoSO's six asset tables exist precisely because a generic table loses billingCycle and billableTo semantics. Subscriptions are typed:
assetsSubscriptions {
toolName varchar(255) notNull src/lib/db/schema.ts:222 // Figma, Vercel, Framer
plan varchar(255) src/lib/db/schema.ts:223 // Pro, Team
cost numeric(12,2) src/lib/db/schema.ts:224 // 15.00, 69.00
billingCycle varchar(50) monthly|yearly notNull src/lib/db/schema.ts:224
renewalDate timestamp notNull src/lib/db/schema.ts:225 // drives cron
billableTo varchar(50) personal|client notNull src/lib/db/schema.ts:227
clientId uuid fk → clients.id src/lib/db/schema.ts:228 // which client if billable
vaultItemId uuid fk → vaultItems.id src/lib/db/schema.ts:229 // login credential
notes text src/lib/db/schema.ts:230
}
// GET joins: leftJoin(clients) + leftJoin(vaultItems) → vaultItemName
// src/app/api/assets/[category]/route.ts:242-263
// POST schema: subscriptionPostSchema validated monthly|yearly, renewalDate coerced, billableTo enum
// src/app/api/assets/[category]/route.ts:79-89What linked gives you: true cost per client billableTo:client + clientId:Acme → $47 vs overhead personal → $42; renewal workflow daysUntil(renewalDate) src/app/api/cron/daily/route.ts:19 → RENEWAL_MILESTONES [7,3,1,0] → notifications + digest Framer renews in 7 days before charge; credential without exposure — login encrypted vaultItems gcm:iv:tag:enc via vaultItemId; profit-aware margin subscriptions cost + hosting costMonthly vs transactions income|expense src/lib/db/schema.ts:87.
The 30-Minute Audit That Finds $600
1. Dump charges. From last 90d statements add toolName,cost,billingCycle,renewalDate,billableTo,clientId — expect 9–14 subs, 3 zombies. 2. Mark billable honestly. billableTo client needs clientId; hesitation = personal overhead. 3. Link vault. Create vaultItems gcm:iv:tag:enc and set vaultItemId — GET shows vaultItemName. 4. Let milestones nag. Check /notifications — 7 days / 3 days / today handles cancel timing. 5. Cancel/downgrade/annual-switch weekly in 7-day window:
| Signal | Action | Saves |
|---|---|---|
Not used in 60 days | Cancel before renewalDate | $10–$40/mo |
Used 2x/mo but Free tier exists | Downgrade plan field, keep row | $15/mo |
Monthly $20 ×12 = $240 vs Yearly $144 and used 10mo | Switch billingCycle yearly, update renewalDate +12mo | $96/yr per tool |
billableTo personal but client reimburses | Flip to client + clientId, add to next invoice items jsonb | Recover $30–$70 |
Freelancers running this find $40–$80/mo zombies = $480–$960/yr. Average is $600 — one audit pays for RunoSO Solo for 3 years.
1Password / LastPass / Sheets vs RunoSO: Who Saves $600?
| Capability | Sheets | 1Password / LastPass | RunoSO Assets + Vault + Cron | ||
|---|---|---|---|---|---|
| Renewal awareness | Cell renewalDate inert | Vault has login expiry, not SaaS renewalDate | renewalDate timestamp src/lib/db/schema.ts:225 → daysUntil() → RENEWAL_MILESTONES [7,3,1,0] src/app/api/cron/daily/route.ts:17 → notifications + email digest | ||
| Billable split | Text Billable? Yes/No Typos Yess | No billing concept | `billableTo personal | client enum src/lib/db/schema.ts:227 + clientId fk src/lib/db/schema.ts:228` — filterable for invoicing | |
| Cycle handling | No logic `monthly | yearly` | None | `billingCycle monthly | yearly src/lib/db/schema.ts:224 validated z.enum src/app/api/assets/[category]/route.ts:83` |
| Credential security | Plaintext password column shared | AES-256 login but no renewalDate link | vaultItems gcm:iv:tag:enc src/lib/vault/crypto.ts:28 + vaultItemId fk → encrypted + linked | ||
| Scale | 14 subs → VLOOKUP fragility | 14 vault entries, no cost aggregate | 6 tables ALLOWED_CATEGORIES src/app/api/assets/[category]/route.ts:91 paginated buildPaginatedQuery + Finance P&L linkage transactions clientId/projectId src/lib/db/schema.ts:97 |
Sheets tells you what you pay. Vault secures login. RunoSO links cost to client, encrypts login, and alerts before capture — plus portalTokens src/lib/db/schema-public.ts:169 lets you share client-billable bundle without exposing personal subs.
Mistakes That Keep Zombies Alive
No renewalDate. Wrong month misses 7-day milestone forever. Cycle in notes. yearly in notes skips billingCycle enum and cron weight. No clientId. Frammer without clientId hides leakage — margin looks $20 worse.
Audit once, save $600 yearly. Link every subscription to its client, encrypt its login, and let milestones ping you before every renewal.
→ Audit Your Stack in RunoSO — Free
By Gautam Parmar — your subscription zombies fear this checklist.
Keep Reading
- Hosting cost twin: Hosting & server management freelancers.
- Portfolio proof: GitHub repo management freelancers.
- Security foundation: 1Password vs RunoSO vault freelance.
FAQ
How does RunoSO know before renewal, not after bank charge? Cron GET /api/cron/daily protected by CRON_SECRET src/app/api/cron/daily/route.ts:24 runs daily, computes daysUntil(renewalDate) src/app/api/cron/daily/route.ts:19 for each assetsSubscriptions row, matches RENEWAL_MILESTONES [7,3,1,0] src/app/api/cron/daily/route.ts:207 and creates notifications type renewal_due with cost body before capture.
Can I track which subscriptions are billable to clients? Yes — billableTo personal|client src/lib/db/schema.ts:227 + clientId src/lib/db/schema.ts:228 lets you filter ?clientId=xxx via GET /api/assets/subscriptions. Invoice line transactions invoiceId then matches billableTo:client totals — Sheets VLOOKUP can't maintain this FK.
Where do SaaS logins live securely? In vaultItems with usernameEncrypted/passwordEncrypted/notesEncrypted/metadataEncrypted src/lib/db/schema.ts:129 encrypted AES-256-GCM gcm:iv:tag:enc src/lib/vault/crypto.ts:22 via deriveKey SHA-256 src/lib/vault/crypto.ts:18. Asset stores only vaultItemId FK src/lib/db/schema.ts:229, UI joins vaultItemName — password never in asset notes.



