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

# Error Codes

> All Crosslink error codes and how to handle them

## Error class

All Crosslink protocol errors are instances of `CrosslinkError`.

```ts theme={null}
class CrosslinkError extends Error {
  readonly code: string;
  readonly data?: unknown;
  toWire(): { code: string; message: string; data?: unknown };
}
```

`code` is one of the values below. These travel on the wire as lowercase, underscore-separated strings and are stable — new codes may be added, existing ones are never repurposed.

***

## Error codes

| `ErrorCodes` key      | Wire value            | Description                                                      |
| --------------------- | --------------------- | ---------------------------------------------------------------- |
| `PARSE_ERROR`         | `parse_error`         | Payload was not valid UTF-8 / JSON                               |
| `INVALID_MESSAGE`     | `invalid_message`     | Message failed structural validation                             |
| `VERSION_UNSUPPORTED` | `version_unsupported` | No mutually supported protocol version                           |
| `UNAUTHORIZED`        | `unauthorized`        | Signature or trust check failed                                  |
| `CAPABILITY_DENIED`   | `capability_denied`   | Device lacks the required capability                             |
| `METHOD_NOT_FOUND`    | `method_not_found`    | RPC method doesn't exist                                         |
| `VALIDATION_FAILED`   | `validation_failed`   | Input failed schema/custom validation                            |
| `PAYLOAD_TOO_LARGE`   | `payload_too_large`   | Message exceeds the session frame limit                          |
| `RATE_LIMITED`        | `rate_limited`        | Too many requests                                                |
| `DEVICE_REVOKED`      | `device_revoked`      | This device's access was revoked                                 |
| `SESSION_EXPIRED`     | `session_expired`     | Handshake timestamp outside the allowed clock skew               |
| `PAIRING_EXPIRED`     | `pairing_expired`     | Pairing code/session expired                                     |
| `PAIRING_INVALID`     | `pairing_invalid`     | Pairing frame rejected (wrong code, cancelled, mismatched nonce) |
| `HOST_OFFLINE`        | `host_offline`        | Host not reachable                                               |
| `TIMEOUT`             | `timeout`             | RPC call timed out                                               |
| `CANCELLED`           | `cancelled`           | Request was cancelled                                            |
| `INTERNAL`            | `internal`            | Unclassified internal error                                      |
| `NOT_CONNECTED`       | `not_connected`       | No live session to send on                                       |
| `PEER_LOST`           | `peer_lost`           | Transport died while a request was in flight                     |
| `GRANT_EXPIRED`       | `grant_expired`       | Capability grant lapsed and must be renewed                      |
| `CONSENT_DENIED`      | `consent_denied`      | Host user declined a per-use confirmation prompt                 |
| `CONSENT_TIMEOUT`     | `consent_timeout`     | Host could not obtain per-use confirmation in time               |
| `POLICY_DENIED`       | `policy_denied`       | Host permission policy forbids this, independent of the grant    |

`CrosslinkError.isInternal(code)` is `true` for `internal`, `parse_error`, and `invalid_message` — codes that must never leak raw detail across the wire.

Fingerprint mismatches during pairing (`client.pairFromQr`) and a few pairing-flow guard checks throw a plain `Error` rather than a `CrosslinkError`, so they have no `code` property — check the message text instead.

***

## Error handling

### Host

```js theme={null}
try {
  await server.start();
} catch (err) {
  console.error("Failed to start:", err);
}
```

### Client

```js theme={null}
try {
  await client.pairFromQr(uri, ["app.control"]);
} catch (err) {
  if (err.message?.includes("fingerprint")) {
    console.error("SECURITY: host fingerprint mismatch -- possible MITM");
  } else if (err.code === "pairing_invalid") {
    console.error("Pairing rejected or cancelled");
  } else {
    console.error("Pairing error:", err);
  }
}
```

### RPC

```js theme={null}
try {
  await rpc.call("app.status");
} catch (err) {
  if (err.code === "capability_denied") {
    console.error("Missing capability");
  } else if (err.code === "method_not_found") {
    console.error("Method not exposed by host");
  } else {
    console.error("RPC error:", err);
  }
}
```

***

## Debug logging

Enable debug logging to trace errors. A logger must implement the full `Logger` interface (`trace`, `debug`, `info`, `warn`, `error`, `child`, `isEnabled`) — use `consoleLogger()` from `@crosslink/core` rather than a partial object literal:

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

const server = createCrosslinkServer({
  // ...
  logger: consoleLogger()
});
```
