All Projects
SaaS API Platform
750HrsTracker
A production-grade ASP.NET Core API that helps U.S. real estate professionals track and prove the 750-hour IRS threshold required for the Real Estate Professional (REP) tax status.
Role: Sole Developer / Backend Lead
Problem Statement
U.S. landlords and real estate professionals can deduct rental losses against ordinary income IF they qualify as a Real Estate Professional under IRS rules — which requires logging at least 750 total hours per year, with over 500 of those being 'material participation' hours. Manually tracking and auditing these hours is error-prone and difficult to document for tax purposes.
Target users: U.S.-based real estate investors, landlords, property managers, and CPAs/tax advisors who need to track, document, and report REP status hours for IRS compliance and tax optimization.
Project Walkthrough
The idea behind 750HrsTracker came from a very specific pain point in U.S. tax law. Real estate investors can deduct rental losses against ordinary income — but only if they qualify as a Real Estate Professional under IRS Section 469(c)(7). That qualification requires logging at least 750 total hours per year in real estate activities, with at least 500 of those being 'material participation' hours. Most investors try to track hours in spreadsheets that won't hold up to audit. I built 750HrsTracker as a purpose-built backend to solve exactly that problem.
The technical core of the system is the REP status calculator — an endpoint that aggregates a user's time logs, categorizes them by IRS-defined material vs. non-material participation type, and calculates real-time progress toward each threshold. But the hours tracking itself is only part of the story. The system also models multiple properties (with year-by-year rental type tracking, since a property might be LTR one year and STR the next), team members (including spouse participation, which counts toward your REP hours under joint filing), and the three different IRS pathways to qualifying status — all encoded as enum values on the data model.
On the infrastructure side, I built on ASP.NET Core 9 with SQL Server and Entity Framework Core. The Stripe integration handles subscription billing via Checkout Sessions — offloading PCI compliance to Stripe while keeping the payment flow simple. One non-trivial piece was building a one-time free trial enforcement mechanism: the HasUsedTrial flag on the user model is checked server-side before any Checkout Session is created, making it impossible to abuse by gaming the frontend. I also implemented a dual-safety subscription expiry system — Stripe webhooks are the primary sync mechanism, but a background hosted service scans the database every hour for subscriptions whose CurrentPeriodEnd has passed, providing a reliable fallback for missed webhooks.
The reporting layer was a deliberate product investment. Users need to export their hours in a format that's defensible to a tax professional or IRS auditor. I implemented both CSV and PDF exports using QuestPDF — generating fully branded, professional-looking documents with custom headers, footers, page numbers, and table layouts, all server-side with no external service dependency.
Tech Stack
Languages
Frameworks
Databases
Cloud & Infrastructure
Dev Tools
Authentication
JWT Bearer tokens (access + refresh token rotation) + ASP.NET Core Identity (password hashing, lockout, email confirmation)
Third-Party APIs & Integrations
Stripe (subscriptions, Checkout Sessions, webhooks, free trial logic)
Mailgun (transactional email — registration, verification, password reset, subscription lifecycle)
Azure Blob Storage (time log file attachments — images, PDFs, docs)
System Architecture
Architecture Pattern
Layered MVC-style monolith with service abstraction layer. Controllers handle HTTP concerns; Services encapsulate business/integration logic; Models define the domain; DTOs provide clean API contracts. A background service runs independently for subscription lifecycle management.
Request Data Flow
Client sends HTTP request → ASP.NET Core routing matches to Controller endpoint → JWT middleware validates Bearer token → Controller validates ModelState / DTO → delegates to Service layer → EF Core translates LINQ to SQL and executes against SQL Server → result mapped to DTO → JSON response returned to client. Background service (SubscriptionExpirationService) runs on a 1-hour timer independently of the request pipeline.
Key Engineering Decisions
1. ASP.NET Core Identity was chosen for proven, battle-tested auth (lockout, email confirmation, password policies). 2. JWT access tokens (short-lived) + persisted refresh tokens (stored in DB, rotatable and revocable). 3. Stripe Checkout Sessions were used instead of direct API charges — offloads PCI compliance to Stripe. 4. A background hosted service (IHostedService) for subscription expiry checks rather than an external scheduler. 5. Azure Blob Storage with a mock fallback allows dev environments to run without cloud credentials. 6. QuestPDF library generates branded PDF reports server-side without any external service dependency.
Database Design
Core tables: Users (extends ASP.NET Identity IdentityUser — adds FirstName, LastName, Company, Role enum, RentalPreference enum, StrLoopholeTarget enum, HasUsedTrial flag). TimeLogs (HoursWorked decimal, StartTime, EndTime, Description, Notes, IsTeam flag — FK to User, Property, Category, Team). Properties (with year-by-year rental type tracking). Teams (with IsSpouseOfPrimaryAccountHolder flag). Subscriptions (UserId, StripeCustomerId, StripeSubscriptionId, PlanId, Status enum). Token tables: EmailVerificationTokens, PasswordResetTokens, RefreshTokens.
Module Structure
Key Features & Implementation
REP Status Calculator (Core Business Logic)
The primary dashboard endpoint calculates a user's Real Estate Professional status in real time — tracking progress toward three IRS thresholds: 500 material participation hours, 250 non-material hours, and 750 total hours. Returns percentages, hours remaining, and status strings.
How it was built
GET /api/dashboard/rep-status queries TimeLogs filtered by userId and date range. Time logs are joined with their Category, and hours are summed by CategoryType (Material vs NonMaterial enums). The three targets (500/250/750) are hardcoded as IRS constants. The endpoint also supports the STR Loophole pathway (StrLoopholeTarget enum on the User model).
Stripe Subscription Billing
Multi-tier subscription management: free plan, paid plans with optional 14-day free trial (one-time per user), Stripe Checkout Session flow, billing portal, subscription status tracking, and webhook event processing.
How it was built
StripeService wraps the Stripe.net SDK. Trial eligibility is checked against the User.HasUsedTrial flag before creating the session — once used, it's permanently marked to prevent abuse. WebhooksController handles Stripe webhook events. SubscriptionExpirationService (IHostedService) runs every 1 hour to catch any subscriptions whose CurrentPeriodEnd has passed.
Dashboard Analytics & PDF Reporting
Multi-dimensional analytics: overview stats, hours by category, hours by property, recent logs. Plus CSV and PDF export of all report types — audit-ready documentation for tax professionals.
How it was built
All dashboard endpoints accept flexible DashboardQueryDto supporting year, startDate, and endDate filters. LINQ GroupBy aggregations compute totals per category/property. PDF export uses QuestPDF 2025 with a fully branded document layout — custom header with the 750HrsTracker logo, dark blue footer with page numbers, and table-formatted content for Detailed/Category/Property reports.
Authentication & Token Management
Full auth flow: register (with email verification required before login), login, token refresh, forgot/reset password, change password, logout, logout-all-devices, soft-delete account.
How it was built
ASP.NET Core Identity manages password hashing (BCrypt.Net), lockout (5 attempts, 5-minute lockout). Refresh token rotation: on each /refresh-token call, the old token is marked revoked with a ReplacedByToken reference and a new token is issued. On password change or logout-all, all refresh tokens for the user are bulk-revoked. Email enumeration is prevented on forgot-password endpoints.
Challenges & Engineering Decisions
STR Loophole Logic Complexity: The IRS has multiple pathways for qualifying as a REP (the standard 750/500-hour path, the 500-hour material participation path, and the '100 hours + more than anyone else' short-term rental loophole). Modeling these as distinct enum values on the User and surfacing the right calculations per pathway required careful domain understanding — this isn't just tracking time, it's encoding tax law into software.
Stripe Webhook Reliability: The solution uses a dual-safety approach: webhooks are the primary sync mechanism, but the SubscriptionExpirationService background job acts as a fallback safety net every hour.
Token Security: The refresh token rotation pattern (revoke-on-use with ReplacedByToken chain) prevents token replay attacks.
API Documentation
API Route Namespaces