@@ -17,6 +17,19 @@ import { supabaseAdmin } from "@/lib/supabase";
1717import { resolveAppUser } from "@/lib/resolve-user" ;
1818import { normalizeGitHubUsername } from "@/lib/validate-github-username" ;
1919
20+ // ─── GitHub API Rate Limiting ──────────────────────────────────────────────────
21+ // Unauthenticated requests: 60 req/hr (shared per IP).
22+ // Authenticated requests (OAuth token or PAT): 5,000 req/hr per user.
23+ // GitHub Search API has an extra secondary limit: ~30 req/min when authenticated.
24+ //
25+ // This route always sends the user's GitHub OAuth token in the Authorization
26+ // header (from NextAuth session), ensuring the 5,000 req/hr limit applies.
27+ // Users can also add a PAT in settings for the same higher limit.
28+ //
29+ // Rate limit errors: GitHub returns HTTP 403 (primary limit) or HTTP 429
30+ // (secondary/search limit). The X-RateLimit-Remaining: 0 response header
31+ // confirms quota exhaustion. The user sees "GitHub API error" in the dashboard.
32+ // ──────────────────────────────────────────────────────────────────────────────
2033export const dynamic = "force-dynamic" ;
2134
2235// ─── GitHub API Rate Limiting ──────────────────────────────────────────────────
@@ -173,6 +186,9 @@ async function fetchContributionsForAccount(
173186 // (unauthenticated, shared per IP) to 5,000 req/hr (per user).
174187 // Without it, multiple users on the same server IP would exhaust
175188 // the shared quota almost immediately.
189+ // Authorization header raises the rate limit from 60 req/hr (unauthenticated,
190+ // shared per IP) to 5,000 req/hr per user. Without it, shared server IPs
191+ // would exhaust the unauthenticated quota almost immediately.
176192 const searchRes = await fetch (
177193 searchUrl . toString ( ) ,
178194 {
0 commit comments