Skip to main content
The whole frontend talks to one configured address — the gateway. apps/web/src/lib/api/httpClient.ts exposes two Axios instances that both point at that single origin and share one auth interceptor. All network calls go through them — never fetch directly, never a fresh Axios instance per feature.

One origin

NEXT_PUBLIC_API_URL is the single backend variable, resolved identically in the browser and on the server — no legacy per-service fallbacks. It points at the gateway (apps/gateway, :3110 in dev), which routes by path prefix to the right upstream (platform-api vs agent-service). The frontend never addresses individual services.
API_ORIGIN lives in the dependency-free routes.ts (not httpClient.ts) so both client and server can import it without pulling Axios in.

Two instances, same origin

Both are createClient(API_ORIGIN)the same base URL. The split is purely for call-site semantics (CRUD vs AI reads better than one client everywhere); it is not two different backends. The gateway decides where each /api/... path actually goes.

Auth interceptor

configureHttpClient(getter) registers one request interceptor on both clients:
The interceptor calls getAuthToken() and, if a token comes back, sets Authorization: Bearer <jwt> (unless the caller already set one). getAuthToken() also caches the last good token; getCachedAuthToken() exposes it synchronously for exit-time keepalive requests (see resumeApi.syncResumeKeepalive), where the page may die before an async token fetch resolves.
Don’t pass tokens manually in feature code. If you’re writing httpClient.api.get(url, { headers: { Authorization: ... } }), the interceptor isn’t wired in that context — fix the wiring, don’t bypass it.

Centralized routes

All paths live in src/lib/api/routes.ts and are all under the /api/* prefix (the gateway’s namespace):
Rules:
  1. No inline path strings — every backend path lives in one of these maps.
  2. API_ROUTES / AGENT_ROUTES hit the gateway; WEB_AGENT_ROUTES are the web app’s own Next.js route handlers (app/api/chat-agent/*) that proxy to the agent.
This is what makes a backend route rename a one-file change instead of a grep-and-pray.

Adding a new endpoint

  1. Add the path to the right map in routes.ts.
  2. Add a typed wrapper in src/lib/api/<feature>.ts returning a typed response.
  3. Use httpClient.api or httpClient.agent — never axios.create() a third client.
  4. If the feature is cloud-only, gate the caller on APP_MODE === 'cloud'.