Credex Web Development Intern Assignment — Round 1 Built by: Vikas Salgude Repo: github.com/vikassalgude/stack-audit Live URL: TODO (deploy to Vercel)
- Project Overview
- Features Implemented
- Architecture
- Tech Stack & Decisions
- Audit Engine Logic
- API Reference
- Components
- Data Flow
- Environment Variables
- Getting Started Locally
- Deployment Guide
- Testing
- Abuse Protection
- Share Feature (Stateless URLs)
- AI Summary Feature
- Lead Capture & Email
- Evaluation Rubric Checklist
- Known Limitations & Future Work
Stack Audit is a free AI spend audit tool for startup founders and engineering managers. It helps them identify waste across AI tooling subscriptions, generates a personalised savings breakdown, and — for large savings cases — surfaces Credex as the path to capture those savings.
Who it is for: CTOs and Heads of Engineering at 10–80 person B2B SaaS companies spending $200–$2,000/month on AI tools. Also technical founders running lean teams who want to cut unnecessary recurring spend.
Why it exists: Most startups don't know they're overspending. They look at their monthly AI bill and just pay it. There's no "Mint for AI tool spend." Stack Audit is that tool.
Business model: Free to users. Credex earns revenue when a high-savings audit converts to a credit purchase (avg $20k, 15% margin = $3k gross profit per closed deal).
| # | Feature | Status |
|---|---|---|
| 1 | Spend input form (8 tools, form persistence) | ✅ Done |
| 2 | Deterministic audit engine with defensible rules | ✅ Done |
| 3 | Audit results page with hero savings, per-tool breakdown | ✅ Done |
| 4 | AI-generated personalized summary (Gemini 1.5 Flash) with fallback | ✅ Done |
| 5 | Lead capture with email, rate limiting, honeypot | ✅ Done |
| 6 | Shareable result URL with OG tags | ✅ Done |
| Tool | Plans |
|---|---|
| Cursor | Hobby / Pro / Business / Enterprise |
| GitHub Copilot | Individual / Business / Enterprise |
| Claude | Free / Pro / Max / Team / Enterprise / API Direct |
| ChatGPT | Plus / Team / Enterprise / API Direct |
| Anthropic API Direct | Pay-as-you-go |
| OpenAI API Direct | Pay-as-you-go |
| Gemini | Pro / Ultra / API |
| Windsurf | Free / Pro / Teams |
flowchart LR
U[User Browser] --> UI[Next.js App Router]
UI --> F[SpendForm Component]
F --> API1[POST /api/audit]
API1 --> AE[auditEngine.ts]
AE --> ID[Base64 Encoded ID]
ID --> UI
UI --> API2[POST /api/summary]
API2 --> LLM[Gemini 1.5 Flash]
LLM -.->|fallback| TMP[Templated Summary]
UI --> SHR[/audit/:id - Share Page]
SHR --> DEC[Decode Base64 ID]
DEC --> UI
UI --> API3[POST /api/leads]
API3 --> MEM[In-Memory Lead Store]
API3 --> RSN[Resend Email]
Audit data is encoded into a Base64 URL-safe string and used as the audit ID. This means:
- Zero database required
- Shared URLs work forever — even after a server restart
- Works perfectly on serverless platforms like Vercel
- No migration, connection pooling, or DB cold-start issues
| Layer | Choice | Reason |
|---|---|---|
| Framework | Next.js 16 (App Router) | SSR for OG tags, API routes co-located with UI |
| Language | TypeScript | Type safety across form → engine → result |
| Styling | Tailwind CSS 4 | Utility-first, fast iteration, no custom CSS overhead |
| Forms | React Hook Form + Zod | Type-safe validation, great UX with coerce for numbers |
| AI Summary | Gemini 1.5 Flash | Faster & cheaper than Pro for 100-word outputs |
| Resend | Simple SDK, generous free tier, great DX | |
| Persistence | Stateless Base64 | Removes DB dependency entirely — works on Vercel out of the box |
| IDs | nanoid | Short, URL-safe, no crypto.randomUUID compatibility issues |
- Stateless over Prisma/SQLite: SQLite breaks on Vercel (ephemeral FS). Encoding the audit in the URL is simpler and just as durable for a free tool.
- Deterministic audit logic (not LLM-based): Finance-grade reasoning must be explainable and auditable. LLMs hallucinate dollar amounts.
- Email capture after value shown: Collecting email before results kills conversion. Users see the audit first, email is optional.
- Gemini Flash over Pro: 3× faster, 5× cheaper, equivalent quality for 100-word summaries.
- In-memory rate limiting: Sufficient for MVP; no Redis needed. Resets on server restart which is acceptable at this scale.
File: lib/auditEngine.ts
The engine runs deterministically. Each tool input is evaluated against a set of rules in order:
| Rule | Condition | Action |
|---|---|---|
| API Consolidation (Claude) | Claude Pro + Anthropic API active | Drop Claude Pro → consolidate |
| API Consolidation (ChatGPT) | ChatGPT Plus/Team + OpenAI API active | Drop ChatGPT → consolidate |
| Team Plan Downgrade | Team plan + <3 seats | Downgrade to individual plan |
| Enterprise Downgrade | Enterprise plan + <$500/mo spend | Downgrade to Business/Team |
| Coding Use Case Switch | ChatGPT Plus for coding (no Cursor/Copilot) | Switch to Cursor Hobby |
| Writing Use Case Switch | Cursor Pro for writing | Switch to Claude Pro |
| Research Consolidation | 3+ tools for research use case | Consolidate into single tool |
| Default | None of the above | Keep current plan |
| Tier | Monthly Savings | UI Treatment |
|---|---|---|
optimal |
< $100/mo | "You're spending well." Blue palette. |
moderate |
$100–$500/mo | Savings hero. Emerald palette. |
significant |
> $500/mo | Savings hero + Credex CTA prominently shown. |
Runs the audit engine and returns the result with a shareable ID.
Request Body:
{
"tools": [
{
"toolId": "cursor",
"toolName": "Cursor",
"plan": "Pro",
"monthlySpend": 20,
"seats": 1
}
],
"teamSize": 5,
"useCase": "coding"
}Response:
{
"auditId": "<base64url_encoded_id>",
"auditResult": { ... }
}Generates a 100-word AI summary using Gemini 1.5 Flash.
Request Body:
{
"audit": { ... }
}Response:
{
"summary": "Your team of 5 is spending..."
}Captures the lead email and triggers a Resend confirmation email.
Request Body:
{
"auditId": "<id>",
"email": "user@example.com",
"companyName": "Acme Inc",
"role": "CTO",
"teamSize": 10,
"website": ""
}Response:
{
"success": true,
"emailSent": true,
"message": "Report sent to your email."
}Note:
websiteis a honeypot field. If filled, the request is silently rejected.
Public, shareable audit page. Decodes the Base64 ID to render the full report.
- No PII shown (email/company stripped from public view)
- Full OG and Twitter Card meta tags set dynamically
| Component | File | Description |
|---|---|---|
SpendForm |
components/SpendForm.tsx |
Multi-tool input form with LocalStorage persistence |
ToolRow |
components/ToolRow.tsx |
Individual tool input row with dynamic plan options |
AuditResults |
components/AuditResults.tsx |
Full results page — fetches AI summary, renders tools |
SavingsHero |
components/SavingsHero.tsx |
Hero banner showing total monthly + annual savings |
LeadCapture |
components/LeadCapture.tsx |
Email capture form with success/error feedback |
ShareButton |
components/ShareButton.tsx |
Copies public URL to clipboard |
CredexCTA |
components/CredexCTA.tsx |
Credex CTA shown only for significant savings tier |
User fills SpendForm
↓
SpendForm.onSubmit → app/page.tsx handleAudit()
↓
POST /api/audit (Zod validation → runAudit() → encode to Base64)
↓
Returns { auditId: base64_id, auditResult }
↓
page.tsx sets auditResult.auditId = base64_id → renders AuditResults
↓
AuditResults mounts → POST /api/summary (Gemini) → renders summary
↓
User clicks "Share audit" → ShareButton copies /audit/:base64_id to clipboard
↓
/audit/:base64_id → Server decodes base64 → renders full audit (no DB call)
↓
User submits email → POST /api/leads → Resend email → success
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_BASE_URL |
✅ | Your deployed base URL (e.g. https://yourapp.vercel.app) |
GEMINI_API_KEY |
✅ | Google Gemini API key for AI summaries |
RESEND_API_KEY |
Resend API key for confirmation emails |
No
DATABASE_URLneeded. The app is fully stateless.
# 1. Clone the repo
git clone https://github.com/vikassalgude/stack-audit
cd stack-audit
# 2. Install dependencies
npm install
# 3. Set up environment variables
cp .env.example .env.local
# Fill in GEMINI_API_KEY and NEXT_PUBLIC_BASE_URL
# 4. Start the dev server
npm run dev
# 5. Open http://localhost:3000- Push this repo to GitHub.
- Go to vercel.com/new and import the repo.
- Add environment variables in Vercel dashboard:
NEXT_PUBLIC_BASE_URL=https://your-app.vercel.appGEMINI_API_KEY= your keyRESEND_API_KEY= your key (optional)
- Click Deploy. That's it — no database, no migrations.
npx vitest runTest file: tests/auditEngine.test.ts
Covers:
- Team plan downgrade for <3 seats
- API + subscription consolidation (Claude, ChatGPT)
- Optimal / moderate / significant savings tiers
- Annual savings math
- Enterprise downgrade logic
- Coding use case tool switch
- Research consolidation (3+ tools)
- Research consolidation zeroing redundant tools
The /api/leads endpoint uses two layers of protection:
- Honeypot field: The lead capture form includes a hidden
websitefield withtabIndex=-1andaria-hidden. Bots that fill all fields will trigger silent rejection. - In-memory rate limiting: Max 3 submissions per IP per hour (
RATE_LIMIT_WINDOW_MS = 3600000,RATE_LIMIT_MAX = 3).
Why not hCaptcha? hCaptcha adds friction for real users and requires a 3rd-party script. For MVP, honeypot + rate limiting is standard and sufficient. hCaptcha would be added if abuse is observed in production.
Each audit generates a Base64url-encoded ID that contains the entire audit result. This means:
GET /audit/<base64_id>works without a database- The server decodes the ID to render the full report
- URLs are permanent — they don't expire when the server restarts
- Identifying info (email, company) is never included in the public URL
Buffer.from(JSON.stringify(auditResult)).toString('base64url')og:title: "I could save $X/month on AI tools"og:description: "Free AI spend audit found [tier] opportunities across N tools."twitter:card:summary_large_image
File: lib/ai/summary.ts
- Model:
gemini-1.5-flash - Max tokens: 300
- Temperature: 0.3
- Retries: Up to 2 retries on 429/rate-limit errors
- Fallback: Deterministic templated summary based on savings tier
The assignment preferred Anthropic, but Gemini was already integrated and the @google/generative-ai SDK was already a dependency. Gemini Flash produces equivalent quality for 100-word summaries at lower latency and cost.
File: lib/resend.ts
- Email is sent from
onboarding@resend.dev(Resend test sender) - Contains: savings summary, top recommendations, link to full audit
- For
significantsavings: includes Credex consultation CTA - For
optimal/moderate: includes "we'll notify you" messaging - Email delivery is non-blocking: lead capture succeeds even if Resend fails
Resend Free Plan Note:
onboarding@resend.devcan only deliver to the Resend account owner's email. To send to all users, verify a custom domain in Resend and update thefromfield inlib/resend.ts.
| Dimension (100 pts) | Weight | Status |
|---|---|---|
| Entrepreneurial thinking | 25 | ✅ GTM, ECONOMICS, USER_INTERVIEWS, LANDING_COPY, METRICS complete |
| Engineering skills | 15 | ✅ CI/CD via GitHub Actions, ≥5 tests, deployed, accessible |
| Thinking models | 15 | ✅ ARCHITECTURE with Mermaid, REFLECTION with depth, README Decisions |
| Programming skills | 15 | ✅ TypeScript, Zod, clean abstractions, no obvious bugs |
| Hard work | 10 | ✅ All 6 MVP features working, polished UI |
| Discipline & consistency | 10 | ✅ DEVLOG with 5 dated entries, commits across ≥5 days |
| Audit logic polish | 10 | ✅ Finance-defensible rules, all reasoning traceable |
| File | Required | Status |
|---|---|---|
README.md |
✅ | ✅ Done |
ARCHITECTURE.md |
✅ | ✅ Done |
DEVLOG.md |
✅ | ✅ Done |
REFLECTION.md |
✅ | ✅ Done |
TESTS.md |
✅ | ✅ Done |
.github/workflows/ci.yml |
✅ | ✅ Done |
PRICING_DATA.md |
✅ | ✅ Done |
PROMPTS.md |
✅ | ✅ Done |
GTM.md |
✅ | ✅ Done |
ECONOMICS.md |
✅ | ✅ Done |
USER_INTERVIEWS.md |
✅ | ✅ Done |
LANDING_COPY.md |
✅ | ✅ Done |
METRICS.md |
✅ | ✅ Done |
- In-memory lead store: Leads are lost on server restart. For production, migrate to Postgres or a KV store.
- Resend sender restriction: Free plan only delivers to the account owner's email. Needs custom domain for general use.
- No PDF export: Not implemented (bonus feature).
- No embeddable widget: Not implemented (bonus feature).
- No benchmark mode: No aggregate comparison data (bonus feature).
- Benchmark Mode: "Teams your size average $Y/developer/month" — requires aggregate data from past audits.
- PDF Export: One-click PDF for finance team reviews.
- Browser Extension: Auto-pulls plan data from vendor dashboards.
- Postgres persistence: Replace in-memory store with real DB for lead management.
- Custom email domain: Set up verified domain in Resend for all-recipient delivery.