About the Project

Inspiration

I'm an international student on a full scholarship, studying in Beijing — and honestly, finding that opportunity felt more like luck than the result of any system designed to help me find it. Scholarships and funded programs are scattered across university sites, foundation pages, and government portals with no central place to search, and the students who need them most — international students, first-generation students, anyone without an established guidance network — are the ones least likely to stumble into the right listing at the right time.

I built Foundwell to be the tool I wish had existed when I was searching: something that actually goes looking for opportunities on your behalf, instead of expecting you to already know where to look.

What it does

Foundwell takes a short profile — your field, degree level, nationality, and where you'd like to study — and searches the live web in real time to find scholarships and funded programs that genuinely fit. Every match comes with a plain-language explanation of why it fits, a source link you can click and verify yourself, and an honest read on the deadline. Once you find something worth applying to, it can also generate a tailored Statement of Purpose strategy for that specific program, and you can save opportunities to a tracker with Kanban and list views, notes, and calendar export.

How I built it

The stack is Next.js 16 (App Router) with TypeScript and Tailwind CSS v4. The two AI features — matching and SOP strategy — run on an open-source model through OpenRouter, and the matching feature is grounded with real web search through the Tavily API.

The architectural decision I care most about is the one that isn't visible in a screenshot: no match reaches the screen without a real, verifiable source. Rather than trusting the model to only cite real programs — which it won't reliably do, especially with smaller open-source models — every match returned by the model is checked, in code, against the URLs that Tavily's search actually returned that session. If a match's source URL doesn't trace back to a real search result, it's filtered out server-side before the response ever leaves the API route. That's not a prompt instruction the model can ignore; it's a hard filter in the response pipeline.

Deadlines get the same honest treatment. Instead of a model guessing whether "Spring 2027 intake" is still open, every match is classified as open (a specific date, confirmed after today), rolling (no fixed deadline), or unclear — and unclear is always shown with a "verify on source" flag rather than silently dropped, because hiding a real opportunity over an ambiguous date does more harm than asking a student to double-check.

Challenges I ran into

The hardest bug I hit was one that looked like an AI reasoning failure but turned out to be a network problem in disguise. The app kept failing with "The search took too many steps to finish" — which reads like the model just couldn't decide when to stop searching. I initially suspected the open-source model itself: maybe it was weaker at knowing when it had "enough" results, or the tool-calling loop had a logic bug.

The actual server log told a different story:

Tavily search error: [TypeError: fetch failed] {
  [cause]: Error [ConnectTimeoutError]: Connect Timeout Error
  code: 'UND_ERR_CONNECT_TIMEOUT'
}

Every single search call was timing out at the TCP-connect stage — before any request or authentication even happened. On a flaky cafe wifi connection, the very first search attempt in a session would succeed, but a later one would silently fail, and the model — following the loop faithfully — would just try a different query instead of retrying the same one, burning through the tool-call budget without ever getting real results back.

The fix ended up being less about the AI and more about defensive engineering: I added an explicit timeout with AbortController, one retry with backoff per search call, and changed the failure message the model receives from "try a different query" to an explicit instruction to stop retrying and either finish with whatever real results it already had or return no matches — rather than looping forever against a broken connection. I extracted that retry logic into a shared helper so both AI routes (matching and SOP strategy) get the same resilience, since a demo failing to a network blip on judging day would be a genuinely avoidable loss.

What I learned

The trust problem was harder — and more interesting — than the search problem. It's easy to get an LLM to produce a plausible-looking scholarship. It's much harder to guarantee it never does, especially once you're not using a model with built-in, verified search. Building the guardrail as a server-side filter, rather than a prompt request, taught me that for anything where being wrong confidently is worse than being unhelpful, the safety property has to live in code the model can't talk its way around.

I also learned that a bug's symptom and its cause can point in completely different directions. Chasing "the model won't stop searching" as an AI problem would have cost me hours tuning prompts for a bug that was actually a ten-line network fix.

What's next

Persisting the application tracker beyond a single browser (accounts and a real database), broadening the search beyond scholarships into fellowships, internships, and research positions, and — closer to home — training the matching prompt on the specific programs and organizations I know from my own scholarship community, so students in similar situations get results that reflect what actually worked for people like them.

About the Project

Inspiration

I'm an international student on a full scholarship, studying in Beijing — and honestly, finding that opportunity felt more like luck than the result of any system designed to help me find it. Scholarships and funded programs are scattered across university sites, foundation pages, and government portals with no central place to search, and the students who need them most — international students, first-generation students, anyone without an established guidance network — are the ones least likely to stumble into the right listing at the right time.

I built Foundwell to be the tool I wish had existed when I was searching: something that actually goes looking for opportunities on your behalf, instead of expecting you to already know where to look.

What it does

Foundwell takes a short profile — your field, degree level, nationality, and where you'd like to study — and searches the live web in real time to find scholarships and funded programs that genuinely fit. Every match comes with a plain-language explanation of why it fits, a source link you can click and verify yourself, and an honest read on the deadline. Once you find something worth applying to, it can also generate a tailored Statement of Purpose strategy for that specific program, and you can save opportunities to a tracker with Kanban and list views, notes, and calendar export.

How I built it

The stack is Next.js 16 (App Router) with TypeScript and Tailwind CSS v4. The two AI features — matching and SOP strategy — run on an open-source model through OpenRouter, and the matching feature is grounded with real web search through the Tavily API.

The architectural decision I care most about is the one that isn't visible in a screenshot: no match reaches the screen without a real, verifiable source. Rather than trusting the model to only cite real programs — which it won't reliably do, especially with smaller open-source models — every match returned by the model is checked, in code, against the URLs that Tavily's search actually returned that session. If a match's source URL doesn't trace back to a real search result, it's filtered out server-side before the response ever leaves the API route. That's not a prompt instruction the model can ignore; it's a hard filter in the response pipeline.

Deadlines get the same honest treatment. Instead of a model guessing whether "Spring 2027 intake" is still open, every match is classified as open (a specific date, confirmed after today), rolling (no fixed deadline), or unclear — and unclear is always shown with a "verify on source" flag rather than silently dropped, because hiding a real opportunity over an ambiguous date does more harm than asking a student to double-check.

Challenges I ran into

The hardest bug I hit was one that looked like an AI reasoning failure but turned out to be a network problem in disguise. The app kept failing with "The search took too many steps to finish" — which reads like the model just couldn't decide when to stop searching. I initially suspected the open-source model itself: maybe it was weaker at knowing when it had "enough" results, or the tool-calling loop had a logic bug.

The actual server log told a different story:

Tavily search error: [TypeError: fetch failed] {
  [cause]: Error [ConnectTimeoutError]: Connect Timeout Error
  code: 'UND_ERR_CONNECT_TIMEOUT'
}

Every single search call was timing out at the TCP-connect stage — before any request or authentication even happened. On a flaky cafe wifi connection, the very first search attempt in a session would succeed, but a later one would silently fail, and the model — following the loop faithfully — would just try a different query instead of retrying the same one, burning through the tool-call budget without ever getting real results back.

The fix ended up being less about the AI and more about defensive engineering: I added an explicit timeout with AbortController, one retry with backoff per search call, and changed the failure message the model receives from "try a different query" to an explicit instruction to stop retrying and either finish with whatever real results it already had or return no matches — rather than looping forever against a broken connection. I extracted that retry logic into a shared helper so both AI routes (matching and SOP strategy) get the same resilience, since a demo failing to a network blip on judging day would be a genuinely avoidable loss.

What I learned

The trust problem was harder — and more interesting — than the search problem. It's easy to get an LLM to produce a plausible-looking scholarship. It's much harder to guarantee it never does, especially once you're not using a model with built-in, verified search. Building the guardrail as a server-side filter, rather than a prompt request, taught me that for anything where being wrong confidently is worse than being unhelpful, the safety property has to live in code the model can't talk its way around.

I also learned that a bug's symptom and its cause can point in completely different directions. Chasing "the model won't stop searching" as an AI problem would have cost me hours tuning prompts for a bug that was actually a ten-line network fix.

What's next

Persisting the application tracker beyond a single browser (accounts and a real database), broadening the search beyond scholarships into fellowships, internships, and research positions, and — closer to home — training the matching prompt on the specific programs and organizations I know from my own scholarship community, so students in similar situations get results that reflect what actually worked for people like them.

USE MY API: OPENROUTER_API_KEY=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 TAVILY_API_KEY=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

Built With

Share this project:

Updates

Submission history