Catch it before it's gone.
CatchIt automatically discovers and publishes career-relevant opportunities for students, researchers, and early-career professionals — hackathons, scholarships, internships, free software/cloud credits, tech events, and conference & journal CFPs — so people stop missing deadlines they'd otherwise never hear about.
Built by Muhammad Ibrahim, Vishnu Ajith, and Muhammed Sihan Haroon.
- Next.js 16 (App Router) · TypeScript · Tailwind CSS v4
- Supabase — Postgres + Auth
- Anthropic API (
@anthropic-ai/sdk) — Claude Haiku 4.5 withweb_searchfor automated discovery - Vercel + Vercel Cron for hosting and scheduling
- Discord incoming webhook for review notifications
Live in production, discovering and serving real opportunities. Full build plan: catchit-build-plan.md.
- Milestone 1 — Scaffold, design system, database schema, Supabase auth plumbing
- Milestone 2 — Public site: feed, filters, search, detail pages, calendar export, submission form, stats, about page, SEO
- Milestone 3 — Admin panel (multi-admin review queue)
- Milestone 4 — Discovery pipeline (Devpost + WikiCFP + AI search + dedup)
- Milestone 5 — Discord webhook integration
- Milestone 6 — Polish, docs, deploy (public JSON API, dead-link checking, OG images, this README)
- Milestone 7 — Weekly email digest (subscribe/confirm/unsubscribe, Resend) + Privacy/Terms pages
- Milestone 8 — Bulk approve/reject in the admin review queue
Each milestone is tagged and published as a GitHub Release with its full changelog.
npm install
cp .env.example .env.local # fill in your Supabase project + keys
npm run devOpen http://localhost:3000.
- Create a project at supabase.com/dashboard.
- Run the migration in
supabase/migrations/via the SQL Editor (orsupabase db pushonce linked). - Optionally run
supabase/seed.sqlfor sample data to develop against. - Copy your Project URL,
anonkey, andservice_rolekey from Project Settings → API into.env.local.
See .env.example for every environment variable the app needs and what each one is for.
Admins are a named allowlist (admin_users table), not a single shared login. To add one:
- Create the Supabase Auth user — either in the dashboard (Authentication → Users → Add user) or via the Admin API:
The response includes the new user's
curl -X POST "$NEXT_PUBLIC_SUPABASE_URL/auth/v1/admin/users" \ -H "apikey: $SUPABASE_SERVICE_ROLE_KEY" \ -H "Authorization: Bearer $SUPABASE_SERVICE_ROLE_KEY" \ -H "Content-Type: application/json" \ -d '{"email":"person@example.com","password":"...","email_confirm":true}'
id. - Add a matching row to
admin_users(SQL Editor, or REST with the service role key):insert into admin_users (id, email, display_name) values ('<id-from-step-1>', 'person@example.com', 'Person Name');
- They can now sign in at
/adminwith that email/password.
Without a matching admin_users row, a signed-in account sees "Not on the admin team" rather than the review queue — signing in alone doesn't grant access.
The pipeline is a single authenticated route, not a special CLI — trigger it the same way Vercel Cron does:
curl -X GET "http://localhost:3000/api/cron/discover" \
-H "Authorization: Bearer $CRON_SECRET"This runs Devpost, WikiCFP, and all six AI-search categories, dedupes against existing rows, inserts survivors as pending, and returns a JSON summary (found/inserted/skipped/failed per source). A full run typically takes 60–120 seconds locally. The same-shaped summary is also logged to the discovery_runs table for later inspection.
Requires ANTHROPIC_API_KEY to be set and to have credits — the AI-search sources will otherwise fail per-query (logged, not fatal) while Devpost/WikiCFP still run.
The dead-link checker follows the same pattern:
curl -X GET "http://localhost:3000/api/cron/check-links" \
-H "Authorization: Bearer $CRON_SECRET"GET /api/opportunities returns the same approved, non-expired feed as the site, as JSON — for embedding in a Moodle/VLE page, a student society site, or anywhere else that shouldn't have to scrape HTML. Supports the same filters as the feed: ?category=, ?region=, ?audience=, ?q=. CORS-open (read-only public data, no auth) and cached for 5 minutes.
- Push this repo to GitHub (already done if you're reading this from the deployed repo), then Import Project on vercel.com.
- Add every variable from
.env.exampleunder Project Settings → Environment Variables —NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY,SUPABASE_SERVICE_ROLE_KEY,ANTHROPIC_API_KEY,CRON_SECRET,DISCORD_WEBHOOK_URL, andNEXT_PUBLIC_SITE_URL(your production domain, no trailing slash).RESEND_API_KEY,DIGEST_FROM_EMAIL, andNEXT_PUBLIC_UMAMI_WEBSITE_IDare optional — each feature degrades gracefully (console-logged emails, no analytics script) when its variable is unset. - Deploy.
vercel.jsonalready defines all three cron jobs — Vercel picks them up automatically:/api/cron/discover— daily at 06:00 UTC/api/cron/check-links— weekly, Monday 07:00 UTC/api/cron/digest— weekly, Monday 08:00 UTC
- Confirm the cron jobs are registered under Project Settings → Cron Jobs, and that the same
CRON_SECRETvalue is set in the environment (Vercel Cron sends it automatically as theAuthorizationheader on scheduled invocations). Note: the Hobby plan has historically capped cron job count/frequency — verify all three actually show scheduled "next run" times, not just accepted at deploy time. - Run the Supabase migrations against your production project if you haven't already (see Database setup above) — this is a separate project from any local/dev Supabase instance you were using.
- Env var changes only take effect on the next deployment — redeploy after adding/changing any variable.
- Every published opportunity links to the original source — CatchIt never rehosts application content.
- No login required to browse; auth is admin-only.
- Row Level Security enforced at the database layer, not just in application code.
- Nothing new is ever auto-approved — every discovered or submitted opportunity lands in the admin queue first.

