{"slug": "i-built-an-ai-job-search-agent-solo-here-s-the-full-stack", "title": "I built an AI job-search agent solo — here's the full stack", "summary": "A developer built Reclaim, an AI job-search agent for engineers, using a stack of Next.js, FastAPI, Supabase, Clerk, Stripe, and Gemini. The AI handles résumé scoring and job matching, routing tasks to different Gemini model tiers to keep costs around five cents per onboarding. The developer shared lessons from production issues, including a Clerk webhook trailing-newline bug and a silent cost leak from an ungated scoring path.", "body_md": "I spent the last couple of months building Reclaim — an AI job-search agent for engineers, done solo, taking up nights and weekends. It reads your résumé, scores it honestly, and matches you against real open roles. It's live at reclaim.careers (free scan, no signup).\n\nThis isn't a launch post — it's a breakdown of the stack and, more usefully, the things that broke. Here's how it's built.\n\nThe stack\n\nFrontend — Next.js on Vercel. App Router, server components where it made sense. Vercel for hosting because the deploy-on-push loop is frictionless and I was optimizing for solo velocity, not infra control.\n\nBackend — FastAPI on Render. I split the Python backend out rather than doing everything in Next API routes, because the heavy lifting (résumé parsing, the matching pipeline, scraping) is Python-native and I wanted it isolated from the frontend's request lifecycle.\n\nDatabase — Supabase + Prisma. Postgres under the hood. Prisma for the schema and type-safe queries; Supabase for the managed Postgres and some auth-adjacent data.\n\nAuth — Clerk. Handles sign-up, sessions, the whole identity layer. More on Clerk below, because it's where I lost the most hours.\n\nPayments — Stripe. Live mode, subscription tiers with a trial. Lookup keys drive tier resolution in the webhook so pricing changes don't require code changes.\n\nThe actual \"AI\" — Gemini. This is the interesting part, so it gets its own section.\n\nGemini does the real work\n\nThe thing I care about most: the AI isn't a chatbot bolted on the side. Gemini makes the actual product decisions.\n\nRésumé reading: parse the PDF, extract real structure, and score it — not against keyword density, but against whether the claims are substantiated. The whole premise is honesty: most AI résumé tools keyword-stuff to beat the ATS, which backfires the second you're in an interview and can't back up your own résumé. Reclaim does the opposite — it flags where you're genuinely strong and where you're stretching.\n\nMatching: scores the résumé against a corpus of 3,000+ real open roles (scraped across hundreds of companies' boards — Greenhouse, Lever, Ashby, Workday), and surface where your actual skills line up vs. where you'd be overreaching.\n\nOne real engineering decision: I route different tasks to different Gemini model tiers based on how much reasoning each needs, to keep per-user cost sane. The free scan runs on a cheaper, faster model; the heavier parse/match work uses a stronger one. Cost per full onboarding lands around five cents, which means cost was never the constraint — distribution and conversion are (a lesson worth its own post).\n\nWhat broke (the useful part)\n\nThe Clerk webhook trailing-newline bug. Production cutover, webhooks silently failing signature verification. The cause: a trailing newline on the signing secret when it got into the environment. Byte-for-byte the secret looked right; it wasn't. Hours lost to something invisible. Lesson: when signature verification fails and the secret \"looks correct,\" check for whitespace first.\n\nA P2002 unique-constraint collision on the webhook path. Stacked webhook events racing to create the same user record, tripping Prisma's unique constraint. Had to make the handler idempotent — upsert semantics instead of naive create.\n\nA silent money leak. A scoring path was calling Gemini for users who weren't entitled to it — burning API spend on people who'd never pay. It \"worked\" (no errors, correct output), which is exactly why it was dangerous: nothing surfaces a cost bug except reading the logs and the bill. Gated it behind the entitlement check. Lesson: a feature that works but shouldn't run is a bug that costs money and throws no error.\n\nTakeaways\n\nSplit the Python out. If your AI work is Python-native, don't force it into your JS framework's request cycle. A separate FastAPI service was worth the extra deploy target.\n\nLookup keys > hardcoded price IDs. Stripe pricing changes shouldn't require a deploy.\n\nThe scary bugs are the ones that don't throw. Trailing newlines, cost leaks, race conditions — they all \"work.\" Reading logs and bills catches what your error handler won't.\n\nCost wasn't the hard part. At ~$0.05/onboarding, the real problem was never the AI bill — it was getting the right people to the site. Building is the easy 20%.\n\nIt's live at reclaim.careers if you want to see it — the résumé scan is free, no signup. Genuinely happy to answer anything about the stack, the Gemini routing, or the scraping in the comments.", "url": "https://wpnews.pro/news/i-built-an-ai-job-search-agent-solo-here-s-the-full-stack", "canonical_source": "https://dev.to/adoomah/i-built-an-ai-job-search-agent-solo-heres-the-full-stack-2fd1", "published_at": "2026-08-01 21:01:24+00:00", "updated_at": "2026-08-01 21:14:39.761558+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-products", "ai-agents", "developer-tools", "generative-ai"], "entities": ["Reclaim", "Next.js", "Vercel", "FastAPI", "Render", "Supabase", "Prisma", "Clerk"], "alternates": {"html": "https://wpnews.pro/news/i-built-an-ai-job-search-agent-solo-here-s-the-full-stack", "markdown": "https://wpnews.pro/news/i-built-an-ai-job-search-agent-solo-here-s-the-full-stack.md", "text": "https://wpnews.pro/news/i-built-an-ai-job-search-agent-solo-here-s-the-full-stack.txt", "jsonld": "https://wpnews.pro/news/i-built-an-ai-job-search-agent-solo-here-s-the-full-stack.jsonld"}}