← All work
Case study · 2026

TrustGraph

Portable, evidence-backed reputation profiles for developers, scored from GitHub, Stack Overflow, Devpost and peer verification.

Next.jsReactTypeScriptTailwind CSSGoChiMongoDBClerkStripeGeminiLive site ↗Source ↗
trustgraph-seven.vercel.app
TrustGraph — screenshot of the live site

Overview

TrustGraph builds a public reputation profile for a developer out of things that can be checked: GitHub repos and merged PRs, Stack Overflow answers, Devpost and Devfolio hackathons, talks, and confirmations from peers. Each profile gets a trust score with a breakdown, a timeline, an embeddable badge, and a recruiter-facing view. The frontend is Next.js 16 and the API is a Go service on Chi backed by MongoDB Atlas.

The problem

Resumes and LinkedIn profiles are self-reported, and the real signal about a developer is spread across a dozen platforms that nobody has time to cross-check. I wanted one profile where every claim points back to a source, the score explains itself, and a recruiter can search across those profiles instead of keyword-matching CVs.

What I built

  • A Go API (Chi router, Clerk JWT middleware, API-key middleware for partners, admin middleware) exposing profiles, trust scores, onboarding, billing, peer verification, disputes and recruiter search under /v1.
  • Connectors for GitHub, Stack Overflow, Devpost, Devfolio and LinkedIn that turn platform stats into typed evidence items (merged_pr, open_source_impact, accepted_answer, hackathon_win and so on) on the profile.
  • A trust score in trust_score.go: four weighted dimensions (evidence depth 0.35, consistency 0.25, peer verification 0.15, impact 0.25), with verified evidence weighted 1.4x, age decay down to a 0.6 floor, log-scaled points for stars and followers, and a cap on how many language items can count.
  • An enrichment agent that pulls web context through Tavily and Firecrawl and asks Gemini for structured JSON insights, falling back to an NVIDIA-hosted model if Gemini fails.
  • Recruiter talent search that parses a free-text query into filters, scores indexed profiles, and can discover more candidates on the web, with saved candidates and async search runs.
  • Background jobs: daily GitHub refresh, weekly Stack Overflow refresh and digest emails, score snapshots for history charts, and outbound webhooks when a score moves by 5 points or more.
  • Shadow profiles seeded in batches from public GitHub rankings, which a person can later claim, plus an SVG badge at /badge/{handle}.svg and an HTML embed widget.

Architecture

The Next.js app talks to a single Go API, which owns all scoring, third-party ingestion and persistence.

flowchart LR
  subgraph client["Client"]
    web["Next.js 16 app"]
    embed["Badge and embed"]
  end
  subgraph server["Go API"]
    router["Chi router and middleware"]
    sync["Profile sync"]
    score["Trust score engine"]
    enrich["Enrichment agent"]
    talent["Recruiter search"]
    jobs["Background job runner"]
  end
  subgraph data["Data"]
    db[("MongoDB Atlas")]
  end
  subgraph ext["External"]
    clerk["Clerk"]
    sources["GitHub, Stack Overflow, Devpost, Devfolio"]
    ai["Gemini and NVIDIA LLMs"]
    web2["Tavily and Firecrawl"]
    stripe["Stripe"]
  end
  web -->|"JWT"| router
  embed --> router
  router --> sync
  router --> talent
  router --> enrich
  sync --> sources
  sync --> score
  enrich --> ai
  enrich --> web2
  talent --> web2
  jobs --> sync
  router --> db
  router --> clerk
  router --> stripe

System design

What happens when a signed-in user connects their GitHub account.

sequenceDiagram
  participant U as User
  participant F as Next.js app
  participant A as Go API
  participant C as Clerk
  participant G as GitHub API
  participant D as MongoDB
  U->>F: Connect GitHub username
  F->>A: POST v1 profiles connect github with JWT
  A->>C: Verify session token
  C-->>A: User id
  A->>G: Fetch repos, merged PRs, languages
  G-->>A: Stats
  A->>A: Map stats to evidence items
  A->>A: Recompute trust score and timeline
  A->>D: Save profile and score snapshot
  A-->>F: Updated profile
  F-->>U: Score reveal
  Note over A,D: Daily job repeats the fetch and fires a webhook if the score moves 5 or more

Decisions & trade-offs

  • Go for the API instead of Next.js route handlers. The ingestion, scoring and scheduled refresh are long-running server work, and keeping them in one typed service was simpler than spreading them across serverless functions. The cost is two deployables and two languages.
  • Clerk for auth, including GitHub sign-in, so I did not need a separate GitHub OAuth app. The GitHub username comes from the Clerk account and evidence is fetched with a server-side token.
  • A hand-tuned, explainable score instead of a learned model. Every dimension and weight is in one file and the API returns positive and negative signals, which matters when the score is about a person. The weights are opinions and need real data to tune.
  • Background jobs run in-process on tickers. Easy to reason about, but they only run while the server is up and would need a proper queue to scale out.

What I'd do next

  • Move refresh and enrichment work onto a job queue so it can run across instances and retry cleanly.
  • Calibrate the score weights against profiles where the outcome is known, instead of setting them by hand.
  • Widen evidence beyond developers, which the professional segment selection in onboarding already anticipates.