Skip to main content
Both platforms share the same Trace account system as the main app (same Supabase project, same JWT). A token minted anywhere in the Trace ecosystem works on either site.

Token storage

src/lib/auth.ts keeps tokens in localStorage:
KeyPurpose
trace_access_tokenShort-lived JWT access token
trace_refresh_tokenLong-lived refresh token
import {
  getAccessToken, getRefreshToken, setTokens, clearTokens,
  isAuthenticated, fetchWithAuth, BACKEND_URL,
} from "@/lib/auth";
BACKEND_URL resolves to process.env.NEXT_PUBLIC_BACKEND_URL || "https://api.buildwithtrace.com".

Email + password login

The login page POSTs directly to the backend, then stores the returned tokens:
curl -X POST https://api.buildwithtrace.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"••••••","captcha_token":""}'

GitHub OAuth

handleGitHubLogin() redirects to the backend, passing a callback that tells it which site to return to:
window.location.href =
  `${BACKEND_URL}/api/v1/auth/github/login?callback=symbols`;     // symbols site
  // …?callback=footprints  on the footprints site
The footprints site sends callback=footprints. The backend must have that callback registered for footprints.buildwithtrace.com or GitHub OAuth will fail there. Email/password login works regardless.

Authenticated requests & auto-refresh

fetchWithAuth(url, options) attaches the bearer token and, on a 401, transparently refreshes via POST /api/v1/auth/refresh and retries once:
const res = await fetchWithAuth("/api/my-symbols?type=symbol", { method: "GET" });
Refresh flow:
POST /api/v1/auth/refresh   { "refresh_token": "…" }
200                       { "access_token": "eyJ…", "refresh_token": "…" }
If refresh fails, clearTokens() is called and the user is signed out.

Which endpoints require auth

Public (no token)
GET
Browse, search, semantic search, suggestions, library detail, categories, thumbnails, download, community list, comment reads, rating reads, health.
Authenticated (Bearer JWT)
POST / PATCH / DELETE
Generate, generate/save, contribute/submit, my-symbols / my-footprints (GET/PATCH/DELETE), posting comments, posting ratings.

Plan gating

Generation is plan-restricted on the backend. Proxy routes surface this directly:
StatusMeaning
401Missing/expired token — sign in again
402 / 403Plan/quota does not include generation (upgrade required)
502 / 504Backend error / generation timeout
The site renders an “Upgrade required” state on 402/403 and links to pricing.