diff --git a/fern/products/browser-sdk/pages/v4/guides/examples/_live-streaming-broadcast.mdx.draft b/fern/products/browser-sdk/pages/v4/guides/examples/_live-streaming-broadcast.mdx.draft
deleted file mode 100644
index bb1cf7c43e..0000000000
--- a/fern/products/browser-sdk/pages/v4/guides/examples/_live-streaming-broadcast.mdx.draft
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: "Live Streaming Broadcast"
-slug: /guides/examples/live-streaming-broadcast
-sidebar-title: "Live Streaming"
-position: 3
-max-toc-depth: 3
----
-
-A one-to-many broadcast app: a small panel of hosts goes live, viewers watch via HLS/RTMP fanout, and viewers can be promoted to the stage on demand.
-
-> **TODO:** Stub. Combine the v3 "interactive-live-streaming" and "streaming-to-youtube" samples.
diff --git a/fern/products/browser-sdk/pages/v4/guides/examples/_overview.mdx.draft b/fern/products/browser-sdk/pages/v4/guides/examples/_overview.mdx.draft
deleted file mode 100644
index 8135bdb42e..0000000000
--- a/fern/products/browser-sdk/pages/v4/guides/examples/_overview.mdx.draft
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: "Overview"
-slug: /guides/examples
-sidebar-title: "Overview"
-position: 0
-max-toc-depth: 3
----
-
-Complete, runnable example apps built with the Browser SDK. Each example pairs a working GitHub repo with a walkthrough explaining the architecture, the key SDK calls, and the trade-offs made.
-
-> **TODO:** Stub. Index and short blurb for each example below.
diff --git a/fern/products/browser-sdk/pages/v4/guides/examples/_video-conference-app.mdx.draft b/fern/products/browser-sdk/pages/v4/guides/examples/_video-conference-app.mdx.draft
deleted file mode 100644
index 05daa5a8bc..0000000000
--- a/fern/products/browser-sdk/pages/v4/guides/examples/_video-conference-app.mdx.draft
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: "Video Conference App"
-slug: /guides/examples/video-conference-app
-sidebar-title: "Video Conference"
-position: 1
-max-toc-depth: 3
----
-
-A Zoom-style multi-party video conference: lobby, grid/presenter layouts, mute controls, screen share, and a roster of participants.
-
-> **TODO:** Stub. Carry forward the v3 "zoom-clone" reference app, ported to v4 (`SignalWire` client, observables, web components where useful).
diff --git a/fern/products/browser-sdk/pages/v4/guides/examples/video-conference-app.mdx b/fern/products/browser-sdk/pages/v4/guides/examples/video-conference-app.mdx
new file mode 100644
index 0000000000..7afe5a9037
--- /dev/null
+++ b/fern/products/browser-sdk/pages/v4/guides/examples/video-conference-app.mdx
@@ -0,0 +1,371 @@
+---
+title: "Video Conference"
+slug: /guides/examples/video-conference-app
+sidebar-title: "Video Conference"
+description: A complete Zoom-style video conference app built on the Browser SDK — backend token minting, a device lobby, multi-party video, in-call controls, screen share, layouts, a roster, and chat.
+position: 1
+max-toc-depth: 3
+---
+
+A Zoom-style multi-party video conference built on the Browser SDK: a backend that mints tokens, a lobby with device selection, multi-party video, in-call controls, screen share, server-side layouts, a participant roster, and chat.
+
+The app has two parts — a small **backend** that exchanges your API token for short-lived user tokens, and a **browser client** that does everything else. Every piece of the client is driven by [RxJS observables][rxjs] on the SDK: nothing polls, everything subscribes and reacts to state the SDK pushes.
+
+
+**Before you start.** You'll need a SignalWire Space, a project (Project ID + API token), and a [user][users] the backend can issue tokens for. Mic and camera access requires an HTTPS origin (`localhost` is the development exception).
+
+
+## Architecture
+
+```mermaid
+flowchart LR
+ subgraph Browser["Browser client"]
+ CP["BackendCredentialProvider"]
+ SW["SignalWire client"]
+ end
+ subgraph Backend["Backend (Node / Express)"]
+ SAT["/api/sat /api/sat/refresh (stores refresh_token)"]
+ end
+ Fabric["SignalWire Fabric"]
+
+ CP -->|"POST + session cookie"| SAT
+ SAT -->|"Basic auth (Project ID + API token)"| Fabric
+ Fabric -->|"token + refresh_token"| SAT
+ SAT -->|"token only"| CP
+ SW <-->|"WebRTC / signaling"| Fabric
+```
+
+The browser **never sees your API token**, and refresh tokens **never reach the browser** — they live server-side, keyed by a session cookie, and rotate on every refresh. The client authenticates by calling the backend, gets a short-lived [Subscriber Access Token][auth] (SAT), and lets the SDK schedule its own refresh through the same backend.
+
+## Project layout
+
+The full runnable app lives alongside this guide:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Run it
+
+
+
+### Configure your environment
+
+Copy `.env.example` to `.env` and fill in your Space, project credentials, and the user the backend issues tokens for:
+
+```bash
+cp .env.example .env
+```
+
+```bash title=".env"
+SW_SPACE=example.signalwire.com
+SW_PROJECT_ID=...
+SW_API_TOKEN=...
+SW_SUBSCRIBER_REFERENCE=...
+SW_SUBSCRIBER_PASSWORD=...
+```
+
+### Install and start
+
+```bash
+npm install
+npm run dev
+```
+
+`npm run dev` starts the backend on port `3001` and the Vite dev server on `5173`. Vite proxies `/api/*` to the backend, so the browser only ever talks to one origin. Open `http://localhost:5173`, click **Sign in** to come online, pick your devices, and **Join** the default room (`/public/test-room`).
+
+
+
+## How it works
+
+### The backend mints tokens
+
+`server/index.ts` exposes two endpoints. `POST /api/sat` exchanges your API token (sent as HTTP Basic auth) for a fresh SAT, stashes the returned `refresh_token` in memory keyed by a session cookie, and returns **only** the `token` and `expires_at` to the browser:
+
+```ts title="server/index.ts"
+app.post('/api/sat', async (req, res) => {
+ const r = await fetch(`${FABRIC_BASE}/subscribers/tokens`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Basic ${BASIC_AUTH}`
+ },
+ body: JSON.stringify({
+ reference: SW_SUBSCRIBER_REFERENCE,
+ password: SW_SUBSCRIBER_PASSWORD
+ })
+ });
+
+ const { token, refresh_token, expires_at } = await r.json();
+ refreshTokens.set(req.sid, refresh_token); // server-side only
+ res.json({ token, expires_at }); // browser sees this
+});
+```
+
+`POST /api/sat/refresh` swaps the stored `refresh_token` for a new SAT **and** a new `refresh_token`, then persists the rotation. Because the refresh token rotates on every call, a leaked one only works once.
+
+
+The demo issues SATs for one hardcoded user and uses an in-memory `Map` keyed by a session cookie that proves nothing. A real app gates `/api/sat` behind your existing user auth and stores refresh tokens in an encrypted, per-user column. See [Authentication][auth].
+
+
+### The client plugs in a credential provider
+
+The SDK accepts a custom [`CredentialProvider`][cred-providers]: an object with `authenticate()` and `refresh()`. `src/credentials.ts` implements both by calling the backend with the session cookie attached, and converts SignalWire's seconds-based `expires_at` into the SDK's ms-based `expiry_at`:
+
+```ts title="src/credentials.ts"
+export class BackendCredentialProvider implements CredentialProvider {
+ async authenticate(_context?: AuthenticateContext) {
+ return fetchSat('/api/sat');
+ }
+
+ // The SDK calls this shortly before the current token expires.
+ async refresh() {
+ return fetchSat('/api/sat/refresh');
+ }
+}
+
+async function fetchSat(path: string) {
+ const r = await fetch(path, { method: 'POST', credentials: 'include' });
+ const { token, expires_at } = await r.json();
+ return { token, expiry_at: expires_at * 1000 }; // seconds → ms
+}
+```
+
+### Connecting and placing calls
+
+`src/main.ts` builds the client with that provider and calls [`register()`][register] — without it, inbound calls never arrive even when someone dials this user:
+
+```ts title="src/main.ts"
+client = new SignalWire(new BackendCredentialProvider());
+await client.register();
+
+bindDevicePickers(client);
+bindIncomingCalls(client);
+bindDirectory(client);
+```
+
+**Device pickers.** The client owns the *currently selected* device for the whole session. The lobby `