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

> ## Agent Instructions
> These docs cover learner integrations through Hosted Lab Pages, the SDK, and the REST API. Content management is outside this integration scope.
> Read the setup page for the chosen approach before implementing it. Keep organization API keys and hosted mint secrets on the server.
> Install the SDK with `npm install @cybr/labs-sdk`. It runs on the server; the `/terminal`, `/terminal/xterm`, `/video`, and `/hosted-browser` entry points run in the browser.
> Cybr provides completion tracking and CTF verification. The integrating platform decides whether to award points.

# Hosted Lab Pages

> CybrHostedLabs for signed launch links, and subscribeToHostedLabEvents for the embedded card.

The [hosted integration guide](/hosted/integration) shows both in context.

## `CybrHostedLabs`

A server client that creates signed launch links. It uses your mint secret, not your organization API key.

```ts theme={null}
import { CybrHostedLabs } from '@cybr/labs-sdk'

const hosted = CybrHostedLabs.init({ mintSecret, mintEndpoint })
```

| Option | Default | Purpose |
| - | - | - |
| `mintSecret` | Required | Mint secret from onboarding |
| `mintEndpoint` | Required | Full HTTPS endpoint from onboarding |
| `timeoutMs` | `30000` | Request deadline, including the response body |
| `fetch` | `globalThis.fetch` | Custom `fetch` implementation |
| `dangerouslyAllowBrowser` | `false` | `init` refuses to run in a browser page. Set this only for a trusted, non-public context. |

### `createLaunchLink(params, options?)`

```ts theme={null}
const link = await hosted.createLaunchLink({
  labId,
  learnerId,
  membership: 'premium',
  displayName: user.name
})
```

| Field | Behavior |
| - | - |
| `labId` | Required |
| `learnerId` | Stable learner ID. Omit it for a new anonymous identity on each link. A blank value throws `TypeError`. |
| `membership` | `free` by default, or `premium` |
| `displayName` | Public name for discussions |
| `memberId` | Optional ID that links rate-this-lab feedback to your member record: 1 to 64 letters, digits, underscores, or hyphens. Separate from `learnerId`. |

Resolves with `{ token, url, embedUrl, expiresAt, learnerId }`. Pass `{ signal }` as the second argument to cancel.

Mint requests are never retried automatically, including rate limits. Failures throw `CybrLabsError`: `401` maps to `invalid_key`, and `429` maps to `rate_limited` with `retryAfter`. The client refuses redirects, so use the exact endpoint from onboarding.

A network failure can follow a successful mint. Repeating a request without `learnerId` creates another anonymous identity.

## Browser events

### `subscribeToHostedLabEvents(options)`

Import from `@cybr/labs-sdk/hosted-browser` in the page that holds the embedded card:

```ts theme={null}
import { subscribeToHostedLabEvents } from '@cybr/labs-sdk/hosted-browser'

const unsubscribe = subscribeToHostedLabEvents({
  hostedOrigin: new URL(link.embedUrl).origin,
  iframeWindow: iframe.contentWindow!,
  labId,
  onCompleted: () => refreshProgress(),
  onEnded: (event) => offerNewLink(event.reason)
})
```

| Option | Purpose |
| - | - |
| `hostedOrigin` | Exact hosted origin, such as `https://labs.cybr.com`. No path or wildcard. |
| `iframeWindow` | The card iframe's `contentWindow` |
| `labId` | Only events for this lab are delivered |
| `onCompleted` | Receives `{ type: 'cybr:lab-completed', labId }` |
| `onEnded` | Receives `{ type: 'cybr:lab-ended', labId, reason? }` |

Returns a function that unsubscribes. Call it when the view closes or the iframe changes.

The helper checks the origin, source window, lab ID, and event shape. Events are best effort, can repeat, and are not proof of completion. Keep award decisions on your server.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.