> ## Documentation Index
> Fetch the complete documentation index at: https://docs.magic-resume.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP clients

> One gateway origin, two Axios instances for call-site semantics, a shared auth interceptor, and centralized routes.

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

```ts theme={null}
// apps/web/src/lib/api/routes.ts
export const API_ORIGIN = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3110'
```

`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.

<Note>
  `API_ORIGIN` lives in the dependency-free `routes.ts` (not `httpClient.ts`) so both client and server can import it without pulling Axios in.
</Note>

## Two instances, same origin

```ts theme={null}
import { httpClient } from '@/lib/api/httpClient'

httpClient.api      // CRUD-style calls (resumes, users, notifications…)
httpClient.agent    // AI-style calls (interview, …)
```

| Instance           | Intent                                                                 |
| ------------------ | ---------------------------------------------------------------------- |
| `httpClient.api`   | Product CRUD — resumes, version history, sharing, users, notifications |
| `httpClient.agent` | AI / long-running — interview and other agent calls                    |

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:

```ts theme={null}
configureHttpClient(async () => clerk.session?.getToken())
```

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.

<Warning>
  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.
</Warning>

## Centralized routes

All paths live in `src/lib/api/routes.ts` and are all under the `/api/*` prefix (the gateway's namespace):

```ts theme={null}
export const API_ROUTES = {
  resumes: {
    list:   '/api/resumes/mine',
    create: '/api/resumes',
    byId:   (id: string) => `/api/resumes/${id}`,
    patch:  (id: string) => `/api/resumes/${id}`,        // see resume-schema for the patch contract
    versions: (id: string) => `/api/resumes/${id}/versions`,
    // …sharing, comments, replies
  },
  users:         { pats: '/api/users/me/personal-access-tokens', /* … */ },
  notifications: { list: '/api/notifications', /* … */ },
  knowledge:     { timelines: '/api/knowledge/timelines' },
}

export const AGENT_ROUTES = {
  interview: { start: '/api/interview/start', chat: '/api/interview/chat', /* … */ },
}

// Next.js route handlers the AI Lab service layer calls (app/api/chat-agent/*)
export const WEB_AGENT_ROUTES = { chat: '/api/chat-agent', chatApprove: '/api/chat-agent/approve', /* … */ }
```

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'`.
