> ## Documentation Index
> Fetch the complete documentation index at: https://crosslink.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# API Reference

> Complete API reference for all Crosslink packages

## Host SDK (`@crosslink/sdk-node`)

### `createCrosslinkServer(options)`

Creates a new Crosslink host server.

```js theme={null}
import { createCrosslinkServer } from "@crosslink/sdk-node";

const server = createCrosslinkServer(options);
```

#### Options

| Property              | Type                                                                                     | Required | Description                                                                                                                                   |
| --------------------- | ---------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `application.id`      | `string`                                                                                 | Yes      | Unique application identifier                                                                                                                 |
| `application.name`    | `string`                                                                                 | Yes      | Human-readable name                                                                                                                           |
| `application.version` | `string`                                                                                 | No       | Semantic version                                                                                                                              |
| `capabilities`        | `CapabilityDef[]`                                                                        | No       | Declared capabilities                                                                                                                         |
| `signalingUrl`        | `string`                                                                                 | No       | Signaling server URL                                                                                                                          |
| `relayUrl`            | `string`                                                                                 | No       | Relay server URL                                                                                                                              |
| `lan`                 | `{ enabled?, port?, bind?, host?, httpHandler? }`                                        | No       | LAN binding options — see [Configuration](/reference/configuration)                                                                           |
| `pairing`             | `{ ttlMs?, autoApprove?, approve?, bootstrapUrl? }`                                      | No       | Pairing configuration                                                                                                                         |
| `networkMode`         | `"auto" \| "local-only" \| "lan-and-relay" \| "remote"`                                  | No       | Which transports the host offers                                                                                                              |
| `remote`              | `{ enabled?, externalPort?, lifetimeSeconds?, autoRenew?, portForwarded?, publicHost? }` | No       | Direct inbound access via router port mapping (PCP/NAT-PMP/UPnP), or a forward you added by hand — see [Remote Access](/guides/remote-access) |
| `onConsentRequest`    | `ConsentPrompt`                                                                          | No       | Asked before each use of a `confirmEachUse` capability                                                                                        |
| `consent`             | `{ alwaysTtlMs?, sessionTtlMs?, promptTimeoutMs? }`                                      | No       | How long consent answers are remembered                                                                                                       |
| `webrtc`              | `{ createPeer, capability?, maxPending?, timeoutMs? }`                                   | No       | Accept WebRTC upgrades from paired devices                                                                                                    |
| `secrets`             | `{ service?, passphrase?, allowPlaintextFallback?, preferFile? }`                        | No       | Secret-store selection and fallbacks                                                                                                          |
| `security`            | `{ maxDevices?, maxActivePairingSessions?, pairingRateLimitMs?, localNetworkOnly? }`     | No       | Security hardening                                                                                                                            |
| `permissions`         | `PermissionPolicy`                                                                       | No       | Host-authored capability policy applied before pairing approval                                                                               |
| `logger`              | `Logger`                                                                                 | No       | Custom logger                                                                                                                                 |

#### CapabilityDef

```ts theme={null}
interface CapabilityDef {
  id: string;           // e.g., "app.control"
  title: string;        // e.g., "Control the app"
  risk: "low" | "medium" | "high";
  description?: string;
  defaultGranted?: boolean; // granted by default at pairing when requested
  confirmEachUse?: boolean; // require a fresh prompt on every use
}
```

#### pairing

```ts theme={null}
{
  ttlMs?: number;
  autoApprove?: boolean; // dev convenience; never for production
  approve?(request: PairingApprovalRequest): PairingApproval | Promise<PairingApproval>;
  bootstrapUrl?: string; // hosted install page for the iOS "Add to Home Screen" flow
}
```

`PairingApproval` is `boolean | string[] | { approved: boolean; caps?: string[] }` — `true` grants everything requested, `false` refuses, and an array or `{ caps }` grants only that subset.

#### PairingApprovalRequest

```ts theme={null}
interface PairingApprovalRequest {
  sas: string;
  deviceName: string;
  deviceId: string;
  requestedCaps: string[];
  requiresExplicitApproval: string[];
  deniedCaps: Array<{ id: string; reason: string }>;
}
```

### Server methods

#### `server.start()`

Start listening for connections.

```js theme={null}
await server.start();
```

#### `server.stop()`

Gracefully shut down.

```js theme={null}
await server.stop();
```

#### `server.getPairingCode()`

Generate a new pairing code.

```js theme={null}
const code = await server.getPairingCode();
// code = {
//   psid: "...",
//   code: "123456789",
//   uri: "crosslink://pair?v=2&e=lan~ws%3A%2F%2F...&c=123456789&a=...&n=...&f=...",
//   expiresAt: 1699999999999,
//   qrSvg: "<svg>...</svg>",
//   endpoints: [{ kind: "lan", url: "ws://192.168.1.5:51820" }]
// }
```

#### `server.expose(method, handler, options?)`

Register an RPC method.

```js theme={null}
server.expose("app.status", () => ({ ok: true }), {
  capability: "app.control"  // Optional capability requirement
});
```

`options` also accepts `inputSchema` (a `MiniSchema`), `validate` (a custom validator function), `idempotent` (safe to auto-retry across reconnects), and `timeoutMs`. `capability` may be an array, in which case all of them are required.

The handler receives `(input, ctx)`:

```ts theme={null}
interface RpcContext {
  deviceId: string;      // which paired device is calling
  requestId: string;
  signal: AbortSignal;   // aborts on client cancel or session loss
  log: Logger;           // pre-bound with device, method, request id
  emitProgress(d: Json): void;  // progress events without ending the request
}
```

See [Capabilities and RPC](/build/capabilities-and-rpc) for how to use them.

#### `server.declareEvent(name, options?)`

Declare an event that can be emitted. `options.capability` gates who may subscribe.

```js theme={null}
server.declareEvent("app.tick");
```

#### `server.emit(name, payload)`

Send an event to all connected clients.

```js theme={null}
server.emit("app.tick", { t: Date.now() });
```

#### `server.listDevices()`

List all paired devices (not only currently connected ones).

```js theme={null}
const devices = server.listDevices();
// [{ deviceId, name, caps, addedAt, lastSeen, revokedAt }]
```

#### `server.revokeDevice(deviceId)`

Revoke a paired device's access and close any of its active sessions. Returns `true` if the device existed.

```js theme={null}
server.revokeDevice(deviceId);
```

#### `server.revokeAllDevices()`

Revoke every currently-paired device.

```js theme={null}
server.revokeAllDevices();
```

#### `server.setDeviceCaps(deviceId, caps)`

Replace a paired device's granted capability set.

```js theme={null}
server.setDeviceCaps(deviceId, ["app.control"]);
```

#### `server.getConnectivity()`

A moment-in-time snapshot of how reachable the host is, with a human-readable `message`.

```js theme={null}
const status = server.getConnectivity();
// { reach: "local-only" | "relayed" | "offline", lan, relay, signaling, webrtc, message, transports }
```

#### `server.connectionEndpoints(mode?)`

Every route a client could use to reach the host right now, in the order a
client should try them. This is the single source of truth for pairing
endpoints — it never invents a route, and a `wan` entry appears only when one
genuinely exists.

```js theme={null}
server.connectionEndpoints();
// [{ kind: "lan", url: "ws://192.168.1.83:57525" },
//  { kind: "wan", url: "ws://203.0.113.9:57525" }]
```

`kind` is one of `"lan" | "wan" | "sig" | "relay" | "tunnel"`. Passing `mode`
filters to what that network mode allows, without changing the host's own mode.

#### `await server.setNetworkMode(mode)`

Changes the running host's actual transport policy. Switching to `"local-only"`
releases router mappings, disconnects signaling and relay services, and
reconnects active devices through a permitted local route. Switching back to a
remote-capable mode starts its configured transports again.

Use this method for settings UI; changing `server.config.networkMode` directly
does not apply live transport lifecycle changes.

#### `server.enableRemoteAccess()`

Opens remote access now, if it is not already open. `setNetworkMode("remote")`
calls it for you; call it directly when a diagnostics UI needs to probe the
router without changing the host's selected mode.

Requires the LAN listener to be bound to all interfaces (`lan: { bind: "all" }`).

#### `server.status()`

A structured snapshot for a diagnostics panel: application info, device id and
fingerprint, active transports, network mode, device count, secret-store
backend, and the permission policy in force.

```js theme={null}
const { secrets, transports, networkMode } = server.status();
```

#### `server.getRemoteDiagnostics()`

What the router said about the last port-mapping attempt (protocol tried, public address, CGNAT detection), or `null` if remote access was never attempted.

#### `server.getInstallHandoff(handoffId)`

Resolves a device-link handoff minted over RPC, used by the iOS "Add to Home
Screen" flow so an installed PWA inherits trust from the browser tab that
paired it. Returns `null` once it is used or expired.

### Server events

Subscribe with `server.typedOn(event, callback)` or the inherited `EventEmitter#on`.

| Event                | Payload                   | Description                      |
| -------------------- | ------------------------- | -------------------------------- |
| `devicePaired`       | `TrustedDeviceRecord`     | A device completed pairing       |
| `deviceRevoked`      | `deviceId: string`        | A device's access was revoked    |
| `deviceConnected`    | `{ deviceId, transport }` | A paired device opened a session |
| `deviceDisconnected` | `{ deviceId, transport }` | A paired device's session closed |
| `pairingIssued`      | `PairingCodeInfo`         | A new pairing code was generated |
| `connectivity`       | `ConnectivityStatus`      | Host reachability changed        |

***

## Client SDK (`@crosslink/sdk-browser`)

### `createSecureCrosslinkClient(options?)`

The preferred browser entry point. Identity and paired-app records are
encrypted at rest under a non-extractable WebCrypto key.

```js theme={null}
import { createSecureCrosslinkClient } from "@crosslink/sdk-browser";

const client = await createSecureCrosslinkClient({ deviceName: "Phone" });
```

Accepts `allowPlaintextFallback` to permit unencrypted storage when
WebCrypto/IndexedDB is unavailable.

### `createCrosslinkClient(options?)`

Synchronous factory that defaults storage to `localStorage`. Use it when you
supply your own `storage`, or need a client without awaiting.

### `CrosslinkClient.create(options?)`

Factory method to create a client whose identity is encrypted at rest with WebCrypto/IndexedDB when available.

```js theme={null}
import { CrosslinkClient } from "@crosslink/sdk-browser";

const client = await CrosslinkClient.create(options);
```

`create()` additionally accepts `allowPlaintextFallback` (boolean), which lets it fall back to unencrypted storage when WebCrypto/IndexedDB is unavailable.

### `new CrosslinkClient(options?)`

Synchronous constructor for embedders that supply their own storage. Without an explicit `storage`, identity lives only in memory for the process lifetime — prefer `CrosslinkClient.create()` in browsers.

```js theme={null}
const client = new CrosslinkClient(options);
```

#### Options

| Property           | Type                                        | Required | Description                         |
| ------------------ | ------------------------------------------- | -------- | ----------------------------------- |
| `deviceName`       | `string`                                    | No       | Device name shown to host           |
| `storage`          | `SecureStorage`                             | No       | Custom storage backend              |
| `onStateChange`    | `(state, detail?) => void`                  | No       | State change callback               |
| `onConfirmPairing` | `(req) => boolean \| Promise<boolean>`      | No       | Pairing confirmation                |
| `logger`           | `Logger`                                    | No       | Custom logger                       |
| `dialTimeoutMs`    | `number`                                    | No       | Connection timeout (default: 10000) |
| `relayToken`       | `string`                                    | No       | Shared secret for a private relay   |
| `networkMode`      | `"auto" \| "local-only" \| "lan-and-relay"` | No       | Connection candidate preference     |

### Client methods

#### `client.pairFromQr(text, requestedCaps?, codeOverride?)`

<Note>
  Applications rarely call this. `CrosslinkMobileBootstrap` (and therefore
  `mobile.entry`) runs pairing, including SAS confirmation, the install handoff
  and the error states. Call it directly only when you are driving a client with
  no Crosslink UI — see [Custom UI](/client/custom-ui).
</Note>

Pair with a host via a scanned QR/URI (or a hosted bootstrap link).

```js theme={null}
const record = await client.pairFromQr(uri, ["app.control"]);
```

#### `client.connect(appId?)`

Connect to a previously paired host. Without `appId`, connects to the first paired app on record. Returns the `RpcClient` once connected.

```js theme={null}
const rpc = await client.connect();
```

#### `client.close()`

Close the current connection.

```js theme={null}
client.close();
```

#### `client.forget(appId)`

Remove a paired host.

```js theme={null}
client.forget("com.example.myapp");
```

#### `client.listApps()`

List all paired hosts.

```js theme={null}
const apps = client.listApps();
```

#### `client.onStateChange(listener)`

Subscribe to connection-state changes; returns an unsubscribe function. This is the only way the client reports connection lifecycle — it is not an event emitter.

```js theme={null}
const unsubscribe = client.onStateChange((state, detail) => {
  console.log("state:", state, detail);
});
```

`state` is one of: `"offline"`, `"discovering"`, `"pairing"`, `"connecting"`, `"direct"`, `"turn-relayed"`, `"crosslink-relayed"`, `"reconnecting"`, `"unauthorized"`, `"revoked"`, `"protocol-incompatible"`.

***

## RPC interface

### `rpc.call(method, args?, options?)`

Call an RPC method. `options.timeoutMs` overrides the default request timeout.

```js theme={null}
const result = await rpc.call("app.status", { detail: true });
```

### `rpc.subscribe(event, callback)`

Subscribe to an event. Returns an unsubscribe function.

```js theme={null}
const unsubscribe = rpc.subscribe("app.tick", (payload) => {
  console.log(payload);
});
```

### `rpc.cancel(requestId)`

Cancel an in-flight request by id.

`RpcClient` has no built-in "connected" / "disconnected" / "error" events, and no `hasCapability` method — capability enforcement happens on the host, and a denied call rejects with a `capability_denied` error (see [Error Codes](/reference/errors)).

***

## Crosslink-owned UI

These are the APIs behind the pairing, bootstrap, install, offline and revoked
experiences. You mount them; Crosslink implements them.

### `createPairingCard(options)`

`@crosslink/sdk-browser` — the canonical desktop pairing UI.

```js theme={null}
createPairingCard({ target: "#crosslink" });
```

The card reads the application's identity and palette from the host's
`application` block, delivered with every pairing session. The options below are
overrides, applied field by field over what the host reported.

| Option               | Type                                       | Description                                                                                                                                                                                                   |
| -------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target`             | `HTMLElement \| string`                    | Where to mount                                                                                                                                                                                                |
| `source`             | `false \| true \| string \| PairingSource` | Omit for the canonical `/__crosslink` source. `true` is a compatibility alias; a string selects another base path; an object supplies a custom transport; `false` explicitly selects advanced controlled mode |
| `appName`            | `string`                                   | Overrides the host's application name                                                                                                                                                                         |
| `appIcon`            | `string`                                   | Overrides the host's icon. Never replaces the Crosslink mark                                                                                                                                                  |
| `brand`              | `CrosslinkTheme`                           | `accentColor`, `backgroundColor`, `textColor`, `appearance` — each overrides the host's                                                                                                                       |
| `blurb`              | `string`                                   | One sentence about what pairing does                                                                                                                                                                          |
| `refreshLeadSeconds` | `number`                                   | Headroom before expiry at which a new code is minted (default 15)                                                                                                                                             |
| `onSession`          | `(session) => void`                        | After a session is minted                                                                                                                                                                                     |
| `onDeviceConnected`  | `(deviceId?) => void`                      | When a paired device connects                                                                                                                                                                                 |
| `onError`            | `(error) => void`                          | After the card renders a failure                                                                                                                                                                              |

Methods: `refresh()`, `setNetworkMode(mode)`, `getNetworkMode()`, `update(state)`,
`setBrand(theme)`, `getBrand()`, `attachSource(source)`, `destroy()`.

`@crosslink/react` exports `CrosslinkPairingCard`, a thin mount around the same
implementation.

### `CrosslinkMobileBootstrap`

`@crosslink/sdk-browser` — the mobile lifecycle: first pair, code entry, SAS
confirmation, Add to Home Screen, Continue in Browser, install handoff, offline,
reconnecting, revoked, and the handoff into your app.

Constructed for you when the host has `mobile.entry`. Construct it directly only
when serving the page from outside a Crosslink host.

Methods: `start()`, `getState()`, `getClient()`, `getEnvironment()`, `destroy()`.

### `describeBootstrapEnvironment()`

`@crosslink/sdk-browser` — what the current origin permits: `secureContext`,
`serviceWorkerAvailable`, `webCryptoAvailable`, `installable`,
`insecureTransportBlocked`, and a `limitations[]` of plain sentences.

### `filterEndpointsForOrigin(endpoints, pageOrigin)`

`@crosslink/core` — splits advertised endpoints into `usable` and `blocked` for a
given page origin. An `https` page cannot open `ws://`; this is what reports that
as a blocked route with a reason rather than a connection timeout.

***

## Host: serving the mobile experience

### `host.createBootstrapHandler()`

An HTTP handler serving your `mobile.entry` plus the manifest, service worker,
icons, browser SDK and install handoff. Mounted automatically on the transport
port when `mobile.entry` is set; call it to mount on a server you already run.

`mobile.attribution` configures the Crosslink attribution footer the bootstrap
mounts on the authorized mobile app shell — `color`, `background`, `size`,
`offset`, `className`. It participates in normal layout flow and there is no
field that removes it. See
[Mobile bootstrap](/client/mobile-bootstrap#the-crosslink-attribution-footer).

### `host.createControlHandler(options?)`

The loopback-only system endpoints the pairing card consumes: `/pairing`,
`/network-mode`, `/devices`, `/revoke`, `/events`, `/widget.js`. Refuses
non-loopback peers itself. `/pairing` answers with the session *and* the host's
`application` block, which is how the card renders your name, icon and colours
without the page repeating them. `options.fallback` handles everything outside the
Crosslink base path, so your own routes sit behind it.

### `host.describeMobileDelivery()`

A deployment-level diagnostic with `mode`, `directLanTransport`,
`secureWssTransport`, `dynamicEndpointDiscovery`, `bootstrapAssetsConfigured`,
`durableOrigin`, and the
origin capabilities `serviceWorkerOriginEligible`, `offlineShell`, `installable`,
and `encryptedDeviceIdentity`. Its multiline `message` labels LAN HTTP versus a
secure published bootstrap. These are configuration/origin capabilities, not a
claim about a particular phone; use `describeBootstrapEnvironment()` in the
page for actual browser runtime support. See [Durable Origins](/client/durable-origin).

### `host.writeStaticBootstrap(outDir, overrides?)`

Writes Crosslink's bootstrap as a static site — `index.html`, `crosslink-sdk.js`,
`crosslink-boot.js`, `sw.js`, `manifest.webmanifest`, `crosslink-mark.svg`, two
generated PNG icons, `.nojekyll`, and any explicitly requested assets. All
framework links are relative, so ordinary static hosting and GitHub Pages
project sites work. It embeds public application branding/capability metadata,
not host endpoints, relay credentials, pairing codes, device keys, or trusted
device records. See [Durable Origins](/client/durable-origin) for its traffic,
trust, and optional-service boundaries.

### `host.bootstrapOrigin()`

The origin a phone should load the bootstrap from: the configured
`pairing.bootstrapUrl` when set, otherwise the best route this host advertises.
