
Solo developer
Super Enrich
Upload a CSV of email addresses and get back company data: industry, headcount, funding, tech stack, whatever fields you ask for. Every value comes back with a source link and a confidence score.
Summary
Upload a CSV of email addresses and get back company data: industry, headcount, funding, tech stack, whatever fields you ask for. Every value comes back with a source link and a confidence score.
Problem
Data enrichment tools tend to break in one of two directions. Open-source tools and raw APIs usually hand you the building blocks and leave the assembly to you: scraping, parsing, rate limits, auth, all of it, before you get a single usable row. Paid platforms like Firecrawl solve that well, but the per-lookup pricing adds up fast once you're running enrichment as a routine task instead of a one-off. I wanted something between the two: no infrastructure to stand up, and no cost curve that punishes regular use. Super Enrich is my answer to that gap, built by extending an existing open-source project rather than starting from an empty editor.
My Contribution
Super Enrich started as Fire Enrich, an open-source demo Firecrawl built to show off their scraping API. I forked it, kept the original MIT license and Mendable AI's copyright exactly as they were, and rebuilt most of the core. The original only worked with Firecrawl for scraping and OpenAI for extraction. I replaced that with a pluggable provider system on both sides: Firecrawl, Tavily, or Serper for search and scraping, OpenAI, Gemini, or OpenRouter for extraction, chosen per run. It also had no concept of a user. I added full multi-user authentication with Better Auth on a Postgres database (Neon, through Drizzle), put a session check in front of every page and API route, and ran a security pass, including an SSRF fix, before treating it as something safe to put in front of the public.
Product Context
The enrichment tool market splits pretty cleanly into two camps. On one end, open-source tools and raw APIs give you the building blocks and leave the assembly to you: scraping, parsing, rate limiting, auth, all of it. On the other end, polished paid platforms, Firecrawl among them, handle everything but charge per lookup in a way that stings once you're running enrichment daily instead of occasionally. Super Enrich sits in between. This isn't a from-scratch build. It's Fire Enrich, Firecrawl's own open-source demo, with the missing pieces added: real authentication, a choice of providers instead of a locked-in one, and the security work needed to run it for more than one person at a time.
What I Worked On
The rebuild covers a few distinct pieces.
Provider abstraction: built a ScraperProvider interface with adapters for Firecrawl, Tavily, and Serper, plus a registry to pick between them at request time. Did the same for LLM extraction across OpenAI, Gemini, and OpenRouter, all running through generateObject on the Vercel AI SDK so every provider returns the same validated shape.
Auth and database foundation: Better Auth plus Drizzle ORM on Neon Postgres. Covers schema, migrations, session helpers, route middleware, sign-out, and a requireApiSession check gating every page and API route that was previously reachable without a login.
Security: found and fixed an SSRF hole where a public-looking URL could redirect to a private or loopback address and slip past the scraper's checks. Every redirect hop gets re-validated now, not just the first URL.
Search and extraction bugs: traced a run of zero-result enrichments back to boolean search queries that weren't grouping the way they read. site:X OR "name" kw1 kw2 has no parentheses, so the OR doesn't scope the way it looks like it should. Fixed that across six pipeline phases. Separately found that Serper's API was only ever returning Google's snippet metadata, never the actual page content, so extraction had been running on empty input the whole time. Also chased down an AI_NoObjectGeneratedError that turned out to be a spec-version mismatch between the AI SDK and the Google and OpenRouter provider packages.
System Workflow
You upload a CSV. The app figures out which column holds the email, no need to name it yourself.
You pick fields, either from a preset (company size, funding, tech stack) or by typing a sentence and letting an LLM turn it into a structured field list.
From there the pipeline runs per row in six phases, each one building on what the last phase learned: Discovery (company name, website, what they do), Profile (industry, headquarters, founding year), Metrics (headcount, revenue), Funding (stage, amount raised, investors), Tech Stack (pulled from the site and GitHub presence), and General (anything custom, using everything the earlier phases already found).
Each phase searches the web in parallel, then hands the results to an LLM that has to fill in a strict Zod schema or return nothing at all. Results stream to the browser as each row finishes, so you watch the table fill in instead of staring at a spinner.
Technical Implementation
Next.js 15 (App Router) and React 19 on the front end, TypeScript throughout, Tailwind with Radix and shadcn for UI.
The enrichment pipeline isn't built on an orchestration framework. It's a custom multi-agent setup where each "agent" is a focused prompt paired with a Zod schema, run through the Vercel AI SDK against whichever LLM provider is selected for that run. Scraping goes through Firecrawl, Tavily, or Serper, also chosen per run (a fourth adapter, TinyFish, is partway built).
Auth is Better Auth, email and password plus optional Google OAuth, sitting on Postgres through Drizzle ORM. Zod validates everything a model hands back before the app trusts it, so a malformed extraction fails loudly instead of quietly polluting a row.
Product & Engineering Decisions
Pluggable providers instead of a locked-in stack. The original Fire Enrich only spoke to Firecrawl and OpenAI. Locking someone into one scraper and one model locks them into one price point and one set of blind spots. A registry pattern on both sides lets someone run cheap and fast with Serper and Gemini, or pay for Firecrawl's better scraping when the data actually needs it.
Phased extraction instead of one-shot. Asking a single model call to guess industry, headcount, funding, and tech stack all at once tends to produce confident-sounding nonsense. Splitting it into phases that each build on the last one's verified output keeps every later phase working from something already grounded in a real source, instead of from the model's own earlier guess.
Kept the original license and attribution. This is built on someone else's open-source work. The MIT license and Mendable AI's copyright stayed exactly as they were in the fork, unmodified.
Impact
The enrichment engine, provider switching, and the auth and database foundation all work end to end today. More specifically: three separate bugs that were silently producing zero usable results (the query grouping issue, the missing Serper content fetch, and the AI SDK version mismatch) are fixed, and that matters more than any feature addition, since an enrichment tool that returns nothing is worse than one that's just slow. The app also went from having no concept of a user to a real multi-tenant auth layer with route-level session gating, which is what makes it possible to run this for more than one person without a rewrite.
What I Learned
Most of what stuck with me here wasn't the part I expected going in.
The search query bug was a good reminder that a string which reads correctly to a human doesn't necessarily parse the way you think on the API side. site:X OR "name" kw1 kw2 looks grouped. Without parentheses it isn't, and Google-style search treats it very differently than the string implies. I only found it by testing the raw query against Serper directly instead of trusting the code.
The OpenRouter and Google extraction failures taught me to stop trusting "same major version" as proof that two packages actually speak the same internal spec. The AI SDK and its provider packages can drift out of sync in ways that only surface as a generic error with no obvious cause.
Doing the SSRF pass before opening this up publicly changed how I think about "done." A demo only has to survive being used the way it was designed to be used. A public tool has to survive someone using it the way it wasn't.