# Co-Browsing API Documentation

> Co-Browsing API allows developers to build co-browsing and instant screen sharing into any product.

Source: https://cobrowsingapi.com/docs

## Table of contents

- [Getting Started](https://cobrowsingapi.com/docs/getting-started.md)
  - [How It Works](https://cobrowsingapi.com/docs/getting-started/how-it-works.md)
  - [What Data Does Upscope Store?](https://cobrowsingapi.com/docs/getting-started/what-data-does-upscope-store.md)
- [SDK](https://cobrowsingapi.com/docs/sdk.md)
  - [Web SDK](https://cobrowsingapi.com/docs/sdk/web.md)
    - [Installation](https://cobrowsingapi.com/docs/sdk/web/installation.md)
    - [Configuration Options](https://cobrowsingapi.com/docs/sdk/web/configuration-options.md)
    - [SDK Functions](https://cobrowsingapi.com/docs/sdk/web/sdk-functions.md)
    - [Listening for Events](https://cobrowsingapi.com/docs/sdk/web/listening-for-events.md)
    - [Multi-Language Support](https://cobrowsingapi.com/docs/sdk/web/translations.md)
  - [iOS SDK](https://cobrowsingapi.com/docs/sdk/ios.md)
    - [Installation](https://cobrowsingapi.com/docs/sdk/ios/installation.md)
    - [Configuration Options](https://cobrowsingapi.com/docs/sdk/ios/configuration-options.md)
    - [SDK Functions](https://cobrowsingapi.com/docs/sdk/ios/sdk-functions.md)
    - [Listening for Events](https://cobrowsingapi.com/docs/sdk/ios/listening-for-events.md)
    - [Full Device Screen Sharing](https://cobrowsingapi.com/docs/sdk/ios/full-device-screen-sharing.md)
  - [Android SDK](https://cobrowsingapi.com/docs/sdk/android.md)
    - [Installation](https://cobrowsingapi.com/docs/sdk/android/installation.md)
    - [Configuration Options](https://cobrowsingapi.com/docs/sdk/android/configuration-options.md)
    - [SDK Functions](https://cobrowsingapi.com/docs/sdk/android/sdk-functions.md)
    - [Listening for Events](https://cobrowsingapi.com/docs/sdk/android/listening-for-events.md)
    - [Full Device Screen Sharing](https://cobrowsingapi.com/docs/sdk/android/full-device-screen-sharing.md)
  - [Flutter SDK](https://cobrowsingapi.com/docs/sdk/flutter.md)
    - [Installation](https://cobrowsingapi.com/docs/sdk/flutter/installation.md)
    - [Configuration Options](https://cobrowsingapi.com/docs/sdk/flutter/configuration-options.md)
    - [SDK Functions](https://cobrowsingapi.com/docs/sdk/flutter/sdk-functions.md)
    - [Listening for Events](https://cobrowsingapi.com/docs/sdk/flutter/listening-for-events.md)
    - [Full Device Screen Sharing](https://cobrowsingapi.com/docs/sdk/flutter/full-device-screen-sharing.md)
  - [React Native SDK](https://cobrowsingapi.com/docs/sdk/react-native.md)
    - [Installation](https://cobrowsingapi.com/docs/sdk/react-native/installation.md)
    - [Configuration Options](https://cobrowsingapi.com/docs/sdk/react-native/configuration-options.md)
    - [SDK Functions](https://cobrowsingapi.com/docs/sdk/react-native/sdk-functions.md)
    - [Listening for Events](https://cobrowsingapi.com/docs/sdk/react-native/listening-for-events.md)
    - [Full Device Screen Sharing](https://cobrowsingapi.com/docs/sdk/react-native/full-device-screen-sharing.md)
  - [Element Masking](https://cobrowsingapi.com/docs/sdk/element-masking.md)
  - [Identifying the Visitor](https://cobrowsingapi.com/docs/sdk/identifying-the-visitor.md)
  - [The Lookup Code](https://cobrowsingapi.com/docs/sdk/the-lookup-code.md)
- [REST API](https://cobrowsingapi.com/docs/rest-api.md)
  - [Authentication](https://cobrowsingapi.com/docs/rest-api/authentication.md)
  - [API Endpoints](https://cobrowsingapi.com/docs/rest-api/api-endpoints.md)
  - [Object Types](https://cobrowsingapi.com/docs/rest-api/object-types.md)
  - [Better Searching](https://cobrowsingapi.com/docs/rest-api/better-searching.md)
  - [Embedded Session Control](https://cobrowsingapi.com/docs/rest-api/embedded-session-control.md)
  - [Embedding with the Viewer SDK](https://cobrowsingapi.com/docs/rest-api/viewer-sdk.md)
- [On Premise](https://cobrowsingapi.com/docs/on-premise.md)
  - [Quick Start](https://cobrowsingapi.com/docs/on-premise/quick-start.md)
  - [REST API (on premise)](https://cobrowsingapi.com/docs/on-premise/rest-api-on-premise.md)
- [Presentation Sharing](https://cobrowsingapi.com/docs/presentation-sharing.md)
  - [SDK Integration](https://cobrowsingapi.com/docs/presentation-sharing/sdk-integration.md)
  - [Getting Public Links](https://cobrowsingapi.com/docs/presentation-sharing/getting-public-link.md)
  - [Salesforce Visualforce Example](https://cobrowsingapi.com/docs/presentation-sharing/salesforce-example.md)

---

## Getting Started

Source: https://cobrowsingapi.com/docs/getting-started

### How It Works

Source: https://cobrowsingapi.com/docs/getting-started/how-it-works

Co-Browsing API allows one person (the **Agent**) to view the screen of another person (the **Visitor**) in real-time.

#### Key Concepts

##### Visitor
The person whose screen is being shared. The Visitor has the SDK installed on their web page or app, and their screen is captured and streamed to the Agent.

##### Agent
The person viewing the Visitor's screen. Agents typically access the session through your dashboard or a custom integration using the REST API.

##### Session
A screen sharing session between one Visitor and one or more Agents. Sessions are temporary and end when either party disconnects.

#### Integration Steps

##### 1. Install the SDK

Add the SDK to the pages or app screens you want to be shareable. See the [SDK documentation](https://cobrowsingapi.com/docs/sdk.md) for platform-specific installation guides.

##### 2. Identify the Visitor

To connect an Agent to a specific Visitor, you need a way to identify them. You have several options:

| Method | Description |
|--------|-------------|
| **Short ID** | Get the Upscope-generated ID via `Upscope('getShortId')` and pass it to your Agent-side code |
| **Unique ID** | Use your own identifier (e.g., user ID from your database) by passing it as `uniqueId` |
| **Integration ID** | Use an ID from another platform (e.g., Intercom, Zendesk) via `integrationIds` |
| **Lookup Code** | Have the Visitor read a [4-digit code](https://cobrowsingapi.com/docs/sdk/the-lookup-code.md) to the Agent over the phone |

If you are using anything other than the Short ID to identify the Visitor, first use the [search endpoint](https://cobrowsingapi.com/docs/rest-api/api-endpoints.md#searching-for-visitors) to get their Short ID.

##### 3. Start a Session

Once you have a Visitor identifier, create a session using the REST API:

1. Call the [start session endpoint](https://cobrowsingapi.com/docs/rest-api/api-endpoints.md#generating-a-link-to-start-screen-sharing-with-a-visitor) with the Visitor's Short ID
2. Receive a `watch_url` in the response
3. Redirect the Agent's browser to this URL

```
POST /v1.3/visitors/visitor_abc/watch_url
{
  "agent": {
    "id": "viewer_123"
  }
}
```

#### What's Next?

- [Install the SDK](https://cobrowsingapi.com/docs/sdk.md) on your web pages or mobile app
- Set up [Visitor identification](https://cobrowsingapi.com/docs/sdk/identifying-the-visitor.md) to find users
- Explore the [REST API](https://cobrowsingapi.com/docs/rest-api.md) for custom integrations

### What Data Does Upscope Store?

Source: https://cobrowsingapi.com/docs/getting-started/what-data-does-upscope-store

Here's a breakdown of the data that Co-Browsing API stores for co-browsing and how long it is retained.

**Data Retention Notice:**
This is not a warranty that data will be kept. Unless specified in your Agreement, data might be deleted earlier at any time without notice.

#### Definitions

| Term | Definition |
|------|------------|
| **Visitor** | Person who shares their screen (has the SDK installed) |
| **Agent** | Person who views a Visitor's screen |
| **Session** | A screen sharing connection between Visitor and Agent(s) |
| **Session Content** | The actual content being shared during a Session |
| **Short ID** | Unique identifier assigned by Upscope to a Visitor |
| **Unique ID** | Identifier you provide for a Visitor |

**Cloud:**

When using Upscope Cloud, data is processed and stored across our infrastructure:
- **Edge** — Datacenter closest to the Visitor (or your chosen region)
- **USA** — Main datacenter in the United States

##### Data Never Sent to Upscope

This data stays in the Visitor's browser and is never transmitted.

| Data | Notes |
|------|-------|
| Page content before Session starts | Only sent once Session is initiated and authorized |
| Masked content | Elements with `no-upscope` class or specified in settings |
| Cookies and browser storage | Never accessed |
| Console content (if disabled) | Only sent if console access is enabled AND Session is active |

##### Data Processed But Not Stored

| Data | Location | Notes |
|------|----------|-------|
| Session HTML content | Edge | During active Session only |
| Audio streams | Edge | If audio is enabled |
| Full screen sharing | Edge | If full screen sharing is enabled |
| Console content | Edge | If console access is enabled, during Session only |
| Agent instructions (highlights, clicks, scroll) | Edge | During active Session only |
| CRM conversation content | USA | If using CRM integrations |

##### Data Cached (24 Hours)

| Data | Location |
|------|----------|
| Public assets (stylesheets, images, fonts) | Edge |

##### Data Stored (1 Week)

| Data | Location | Notes |
|------|----------|-------|
| Server request logs | USA | Redacted, DevOps access only |
| Application telemetry | USA | How users interact with Upscope |
| Sample connection logs | USA | Random sample for debugging |

##### Visitor Data Stored (30 Days)

Stored until 30 days after Visitor's last activity, manual deletion, or account deletion.

| Data | Location | Notes |
|------|----------|-------|
| Last IP address | Edge | |
| Last page view (timestamp, URL) | Edge | |
| Country and city | Edge | Derived from IP, not GPS |
| Device info (browser, device type) | Edge | |
| Online status | Edge | |
| Unique ID | Edge | If provided |
| Identities (name, email) | Edge | If provided |
| REST API data | USA | Synced from Edge for API access |

**High-volume accounts** may have Visitor data auto-deleted after 24 hours.

##### Data Stored Until Account Deletion

| Data | Location | Notes |
|------|----------|-------|
| Visitor data from Sessions | USA | IP, Unique ID, identities |
| REST API call logs | USA | Redacted |
| Session recordings (MP4) | Edge | If recording enabled |
| Webhook logs | USA | Without request body |
| Usage statistics | USA | Session counts, lengths (no content) |
| Credit card details | USA | |
| OAuth keys | USA | For integrations |

##### Data Stored 1 Year After Account Deletion

| Data | Location |
|------|----------|
| Session history | USA |
| Agent account details | USA |
| Account settings changes | USA |

##### Data Stored Indefinitely

| Data | Location |
|------|----------|
| Contracts | USA |
| Billing history and details | USA |
| Communications with Upscope | USA |

**On-Premise:**

When using an on-premise installation, the co-browsing bridge server runs within your infrastructure. This significantly reduces data sent to Upscope.

- **Your Infrastructure** — Where your on-premise server is hosted
- **USA** — Upscope's main datacenter (limited data only)

##### Data Never Sent to Upscope

This data stays in the Visitor's browser and is never transmitted.

| Data | Notes |
|------|-------|
| Page content before Session starts | Only sent once Session is initiated and authorized |
| Masked content | Elements with `no-upscope` class or specified in settings |
| Cookies and browser storage | Never accessed |
| Console content (if disabled) | Only sent if console access is enabled AND Session is active |

##### Data Processed But Not Stored

| Data | Location | Notes |
|------|----------|-------|
| Session HTML content | Your Infrastructure | During active Session only |
| Audio streams | Your Infrastructure | If audio is enabled |
| Full screen sharing | Your Infrastructure | If full screen sharing is enabled |
| Console content | Your Infrastructure | If console access is enabled, during Session only |
| Agent instructions (highlights, clicks, scroll) | Your Infrastructure | During active Session only |

##### Data Cached (24 Hours)

| Data | Location |
|------|----------|
| Public assets (stylesheets, images, fonts) | Your Infrastructure |

##### Visitor Data Stored (30 Days)

Stored on your infrastructure until 30 days after Visitor's last activity or manual deletion.

| Data | Location | Notes |
|------|----------|-------|
| Last IP address | Your Infrastructure | |
| Last page view (timestamp, URL) | Your Infrastructure | |
| Country and city | Your Infrastructure | Derived from IP, not GPS |
| Device info (browser, device type) | Your Infrastructure | |
| Online status | Your Infrastructure | |
| Unique ID | Your Infrastructure | If provided |
| Identities (name, email) | Your Infrastructure | If provided |

**On-Premise REST API:**
Use the on-premise REST API to keep Visitor data within your infrastructure. Using the Cloud REST API would sync data to Upscope's USA datacenter.

##### Data Stored Until Account Deletion

| Data | Location | Notes |
|------|----------|-------|
| Session recordings (MP4) | Your Infrastructure | If recording enabled |

**The following is still stored by Upscope Cloud:**

| Data | Location | Notes |
|------|----------|-------|
| Webhook logs | USA | Without request body |
| Usage statistics | USA | Session counts, lengths (no content) |
| Credit card details | USA | |
| OAuth keys | USA | For integrations |

##### Data Stored 1 Year After Account Deletion

| Data | Location |
|------|----------|
| Agent account details | USA |
| Account settings changes | USA |

##### Data Stored Indefinitely

| Data | Location |
|------|----------|
| Contracts | USA |
| Billing history and details | USA |
| Communications with Upscope | USA |



## SDK

Source: https://cobrowsingapi.com/docs/sdk

Co-Browsing API needs to be installed on any web page or app you want to be shared.

Choose your platform to get started with the SDK:

- **[Web](https://cobrowsingapi.com/docs/sdk/web/)** - JavaScript SDK for web applications
- **[iOS](https://cobrowsingapi.com/docs/sdk/ios/)** - Native iOS SDK for Swift/SwiftUI apps
- **[Android](https://cobrowsingapi.com/docs/sdk/android/)** - Native Android SDK for Kotlin apps
- **[Flutter](https://cobrowsingapi.com/docs/sdk/flutter/)** - Flutter SDK for cross-platform apps
- **[React Native](https://cobrowsingapi.com/docs/sdk/react-native/)** - React Native SDK for cross-platform apps

Once installed, check out these guides:

- **[Identifying the Visitor](https://cobrowsingapi.com/docs/sdk/identifying-the-visitor.md)** - Set user identities for agent search
- **[Element Masking](https://cobrowsingapi.com/docs/sdk/element-masking.md)** - Hide sensitive content during sessions
- **[The Lookup Code](https://cobrowsingapi.com/docs/sdk/the-lookup-code.md)** - Quick connection codes for agents

### Web SDK

Source: https://cobrowsingapi.com/docs/sdk/web

#### Installation

Source: https://cobrowsingapi.com/docs/sdk/web/installation

##### Overview

Co-Browsing API can be installed using one of the following methods:

1. **Installing via Script Tag** - Directly add the code snippet to your web page.
2. **Installing via NPM** - Ideal for projects using a Node.js environment.
3. **Installing via React** - For React-based applications.

Choose the method that best fits your project's requirements.

**Installing via Script Tag:**

You'll find your installation code within your Co-Browsing API dashboard. Simply add the code anywhere on your webpage, or, if you prefer, add it to a JavaScript file by removing the `<script>` and `</script>` tags from the code.

**Recommendation:** Make sure that the code loads as fast as possible by adding it as one of the first things that execute on the page.

**Warning:**
While you can install Co-Browsing API through Google Tag Manager or Segment, the preferred method is to paste the installation code directly on your website, as this will result in faster load times.

Installation remains the same whatever front end framework you use. Co-Browsing API works fine with React, Angular, and most other modern JavaScript frameworks. All you need to do is add the code to the page.

**Installing via NPM:**

1. Install the SDK

   ```shell
   npm install --save @upscopeio/sdk
   ```

2. Import the Upscope Object

   ```javascript
   import Upscope from '@upscopeio/sdk';
   ```

3. Initialize

   ```javascript
   Upscope("init", {
     apiKey: "<public_api_key>"
   });
   ```

**Note:** You can use the Upscope object wherever required, and call the same functions that are available with the regular installation. Initialization (`init`) needs to be called first and must include your public API key.

###### Public API Key

You can find yours in the installation page of your Co-Browsing API dashboard.

###### Pinning a Specific Version

By default, the package downloads the latest version of the Co-Browsing API code, so you are always up to date without reinstalling. If you prefer to run the exact version you installed from npm, import from `@upscopeio/sdk/static` instead:

```javascript
import Upscope from '@upscopeio/sdk/static';
```

The static import works exactly like the default one, but no remote code is downloaded: only your account configuration is fetched, and the Co-Browsing API code that runs is the one bundled with the installed package version. This also makes it suitable for environments that disallow remote code, such as Manifest V3 browser extensions.

**Get notified about new versions:**
When pinning a version, updates only reach your users when you update the package and redeploy. Add your email address on the installation page of your Co-Browsing API dashboard to be notified when a new version is released.

**Installing via React:**

###### Installation

To incorporate Co-Browsing API into your React project, first install the React-specific package using npm:

```shell
npm install --save @upscopeio/react
```

Import the `UpscopeProvider` component and wrap your main application component with it.

```javascript
import { UpscopeProvider } from '@upscopeio/react';
<UpscopeProvider apiKey="<public_api_key>" enabled={true/false}>
  {/* rest of your app */}
</UpscopeProvider>
```

**Entire page shared:**
Co-Browsing API will share the entire page, regardless of where the `UpscopeProvider` is added in your component hierarchy. To only share a specific part of your content, see [Sharing Only Part of the Page](#sharing-only-part-of-the-page).

###### Pinning a Specific Version

By default, the package downloads the latest version of the Co-Browsing API code, so you are always up to date without reinstalling. If you prefer to run the exact version you installed from npm, import from `@upscopeio/react/static` instead:

```javascript
import { UpscopeProvider } from '@upscopeio/react/static';
```

All the other exports (`useUpscope`, `Masked`, `NoRemoteControl`) are available from the same path. The static import works exactly like the default one, but no remote code is downloaded: only your account configuration is fetched, and the Co-Browsing API code that runs is the one bundled with the installed package version. This also makes it suitable for environments that disallow remote code, such as Manifest V3 browser extensions.

**Get notified about new versions:**
When pinning a version, updates only reach your users when you update the package and redeploy. Add your email address on the installation page of your Co-Browsing API dashboard to be notified when a new version is released.

###### Configuration

###### Public API Key

You can find yours in the installation page of your Co-Browsing API dashboard.

The `UpscopeProvider` accepts props that you can use for additional configuration settings. These settings are similar to the ones you would specify using the `init` function in the standard SDK. For example, to specify a unique identifier for a user, you can do:

```javascript
<UpscopeProvider apiKey="<public_api_key>" enabled={true} uniqueId={user.email}>
  {/* Your application code here */}
</UpscopeProvider>
```

###### Using Functionality in Components

To use Co-Browsing API features in your individual components, you can use the `useUpscope` hook:

```javascript
import { useUpscope } from '@upscopeio/react';
function YourComponent() {
  const {
    Upscope,       // SDK object
    shortId,       // Connected shortId or undefined
    getLookupCode, // Asynchronous function to get lookup code
    listen,        // Event listener function
    reset,         // Reset function
    isSharing      // Boolean indicating active session
  } = useUpscope();
  // Your component logic here
}
```

###### Additional Utilities: Masking and Disabling Remote Control

Co-Browsing API offers utility components to mask sensitive data and disable remote control on specific UI elements.

To mask sensitive information:

```javascript
import { Masked, NoRemoteControl } from '@upscopeio/react';
function YourComponent() {
  return (
    <>
      <Masked>
        {/* Sensitive Info */}
      </Masked>
      <NoRemoteControl>
        {/* Control Elements */}
      </NoRemoteControl>
    </>
  );
}
```

To disable remote control on a particular element:

```javascript
import { NoRemoteControl } from '@upscopeio/react';
function YourComponent() {
  return (
    <>
      <NoRemoteControl>
        <label>
          Accept Terms of Service
          <input type="checkbox" />
        </label>
      </NoRemoteControl>
    </>
  );
}
```

By following these steps and guidelines, you can fully integrate Co-Browsing API into your React application and leverage its features effectively.

##### Testing on a Local or Staging Environment

When you test on your own computer or in a staging environment that is not publicly accessible, you might notice some odd rendering issues.

This is because our proxy server is unable to reach your CSS and media files and therefore can't properly edit them to render on the Agent side.

We try to automatically detect if you are on a URL that looks like localhost (e.g. `http://127.0.0.1/*`, `http://localhost/*`, etc.), and send the content of CSS files directly from the Visitor's browser to the Agent's browser.

You can add more URLs for browser proxying in your dashboard settings.

##### Iframe Support

Co-Browsing API will work with iframes without you needing to do anything when these are hosted on the same domain. This means that the part of the URL between `://` and the first `/` is exactly the same (i.e. `app.acme.com` and `dashboard.acme.com` are considered different domains).

In this case, you only need to add the script to the outermost frame (i.e. the parent page).

You don't need to do anything to make Co-Browsing API work cross-domain if iframes aren't involved.

###### Different Domains

To make Co-Browsing API work when you have iframes on different domains/subdomains, you'll need to add the code to all the iframes. This is the code you get from your dashboard's installation page.

The iframes will connect automatically.

**Using the SDK:**
The code will behave differently in the iframe, and you can't use SDK within it.
That means that if you want to identify the user you'll need to do so in the outermost frame.

###### Sharing Only Part of the Page

By default, Co-Browsing API shares the entire page. If you only want to share a specific part of your content (for example, a document viewer or a preview area), place that content in a **same-origin iframe** and pass the iframe's `contentWindow` as the `sharingRoot` configuration option. Only the iframe's document will be shared — everything outside it stays private.

```html
<iframe id="shared-content" src="/shared-content"></iframe>
```

```javascript
const iframe = document.querySelector('#shared-content');
iframe.addEventListener('load', () => {
  Upscope('init', {
    sharingRoot: iframe.contentWindow
  });
});
```

Make sure the iframe has finished loading before initializing, as shown above, so that `contentWindow` points to the final document.

If you're using React, pass `sharingRoot` as a prop to the `UpscopeProvider`, enabling it once the iframe is available:

```javascript
function SharedContent() {
  const [contentWindow, setContentWindow] = useState(null);
  return (
    <UpscopeProvider
      apiKey="<public_api_key>"
      enabled={!!contentWindow}
      sharingRoot={contentWindow}
    >
      <iframe
        src="/shared-content"
        onLoad={(e) => setContentWindow(e.target.contentWindow)}
      />
    </UpscopeProvider>
  );
}
```

**Same origin required:**
The `sharingRoot` window must be on the same origin as the page running the SDK. Cross-origin iframes cannot be used as the sharing root; for those, follow the [Different Domains](#different-domains) instructions instead.

##### When You Have a Lot of Visitors

If your website has a lot of Visitors (i.e. over 5,000 connected at once), you might want to only connect Visitors who actually need help.

You can easily do this by passing `autoconnect: false` to the configuration, like this (or by turning this off in the dashboard):

```javascript
// Rest of the installation code...
Upscope('init', {
 autoconnect: false
})
```

The visitor will be connected automatically if they have recently been in a Session, and will also automatically connect if they are shown the **[lookup code](https://cobrowsingapi.com/docs/sdk/the-lookup-code.md)** through any means.

You can also manually connect the Visitor by calling `Upscope('connect');`

##### CSP Rules

If you use Content Security Policy rules to protect your website, you'll need to add the following URLs to allow Co-Browsing API to work correctly.

```diff
script-src 'self' https://code.upscope.io https://js.upscope.io;
connect-src wss://*.upscope.io https://*.upscope.io;
media-src https://js.upscope.io;
img-src https://app.upscope.io https://app-cdn.upscope.io;
```

##### Prototype.js

To make Co-Browsing API compatible with some older versions of Prototype, include the following code before the installation code.

```html
<script>
  if (window.Prototype) {
    delete Object.prototype.toJSON;
    delete Array.prototype.toJSON;
    delete Hash.prototype.toJSON;
    delete String.prototype.toJSON;
  }
</script>
```

#### Configuration Options

Source: https://cobrowsingapi.com/docs/sdk/web/configuration-options

Co-Browsing API's installation code includes the `Upscope('init', {});` function, which accepts a dictionary of options as its second parameter. Most of these options can be configured through the Co-Browsing API dashboard.

Some settings might not be available with your plan or may only be accessible if you have beta features enabled.

**Please use the dashboard:**
Setting these values through JavaScript is only recommended if you can't configure them through the dashboard and have a specific use case where each page needs to behave differently. Values set through JavaScript will override your dashboard settings. You can configure most of these settings in the dashboard.

##### Identifying the Visitor

You can use these settings to identify the visitor within Co-Browsing API.

| Option           | Default Value | Description                                                                                                                                                                                                                            |
| ---------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `identities`     | `undefined`   | A list of strings to identify the visitor by (e.g. `['Joe Smith', 'joe@smith.com']`). If set to `null`, the identity info is cleared. If not set, nothing is changed. With some integrations, this is set automatically if left empty. |
| `tags`           | `undefined`   | A list of strings to tag the visitor with (e.g. `['#visitor', '#high-value']`). Tags can only be alphanumeric characters and cannot contain spaces. If set to `null`, the identity info is cleared. If not set, nothing is changed.    |
| `uniqueId`       | `undefined`   | A string to uniquely identify the visitor (e.g. `123`). If set to `null`, the ID is cleared. If not set, nothing is changed. With some integrations, this is set automatically if left empty.                                          |
| `integrationIds` | `undefined`   | A list of IDs that can be used to link the visitor to different records. Example: `["system_name:system_value"]`. New integration IDs are added to existing ones unless cleared like this: `["system_name:"]`.                         |
| `metadata`       | `undefined`   | A `Record<string, string>` object with metadata related to the visitor.                                                                                                                                                                |
| `secretKey`      | `undefined`   | An optional string required in all pageviews to match. If it doesn't match, a new visitor will be generated. There is no way to retrieve the `secretKey`, so no other actors would have access to it.                                  |

##### Agent Prompt

Use these settings to show extra information about the visitor to the agent. This can also be used to provide the agent with instructions on troubleshooting common problems with the specific page.

| Option        | Default Value | Description                                                                             |
| ------------- | ------------- | --------------------------------------------------------------------------------------- |
| `agentPrompt` | `undefined`   | A string of text containing information to be presented to the agent about the visitor. |

##### Additional Configuration

These settings control how the session behaves on this particular page.

| Option                           | Default Value                       | Description                                                                                                                                                                                                                                                                                                                                                       |
| -------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowAgentRedirect`             | (Set through the admin interface)   | Whether to allow agents to change the URL for the visitor.                                                                                                                                                                                                                                                                                                        |
| `allowFullScreen`                | (Set through the admin interface)   | Whether to allow full screen mode during sessions.                                                                                                                                                                                                                                                                                                                |
| `allowRequestFullTab`            | (Set through the admin interface)   | Whether to allow agents to request full tab sharing.                                                                                                                                                                                                                                                                                                              |
| `allowRemoteClick`               | (Set through the admin interface)   | Whether to allow agents to click for the visitor.                                                                                                                                                                                                                                                                                                                 |
| `agentRequestButtonPages`        | (Set through the admin interface)   | Pages on which to show the agent request button (e.g. `['https://site.com/help/*']`).                                                                                                                                                                                                                                                                             |
| `agentRequestButtonStyle`        | (Set through the admin interface)   | Position and style of the agent request button.                                                                                                                                                                                                                                                                                                                   |
| `allowRemoteConsole`             | (Set through the admin interface)   | Whether to allow agents (who have the right permissions) to execute remote console commands.                                                                                                                                                                                                                                                                      |
| `allowRemoteScroll`              | (Set through the admin interface)   | Whether to allow agents to scroll for the visitor.                                                                                                                                                                                                                                                                                                                |
| `allowRemoteType`                | (Set through the admin interface)   | Whether to allow agents to use the visitor's keyboard.                                                                                                                                                                                                                                                                                                            |
| `apiKey`                         | (Automatically set to your API key) | The API key of the account to connect to.                                                                                                                                                                                                                                                                                                                         |
| `autoconnect`                    | (Set through the admin interface)   | Whether to connect to the server automatically.                                                                                                                                                                                                                                                                                                                   |
| `automaticallyRequestFullTab`    | (Set through the admin interface)   | Whether to automatically request full tab sharing when a session starts.                                                                                                                                                                                                                                                                                          |
| `callRingtone`                   | (Default ringtone)                  | An mp3 of the ringtone for audio calls.                                                                                                                                                                                                                                                                                                                           |
| `collectHistory`                 | (Set through the admin interface)   | Whether to take screenshots and record pageviews to show in integrations.                                                                                                                                                                                                                                                                                         |
| `cursorColor`                    | `null`                              | The color to use for the enlarged cursor.                                                                                                                                                                                                                                                                                                                         |
| `disableFullScreenWhenMasked`    | `false`                             | Whether to disable full screen mode when masked elements are present on the page.                                                                                                                                                                                                                                                                                 |
| `disconnectAfterSeconds`         | `900`                               | Number of seconds of inactivity after which the SDK disconnects from the server. This only applies if a session is not active, and the connection is re-established when the tab regains focus, the cursor is moved, or the keyboard is used.                                                                                                                     |
| `domChangesDelay`                | `100`                               | Refresh rate of the page. Set to 100 so changes are shown right away, but can be higher on websites where a lot changes constantly to avoid the agent's browser slowing down.                                                                                                                                                                                     |
| `drawingsTtlMs`                  | `6000`                              | How long to keep agent drawings visible for.                                                                                                                                                                                                                                                                                                                      |
| `enableCanvases`                 | `true`                              | Whether to show canvases while screen sharing.                                                                                                                                                                                                                                                                                                                    |
| `enableLookupCodeOnKey`          | (Set through the admin interface)   | Whether to show the lookup code when the visitor presses the `lookupCodeKey` `lookupCodeKeyRepetitions` times.                                                                                                                                                                                                                                                    |
| `enableSessionRating`            | (Set through the admin interface)   | Whether to show a session rating prompt to the visitor after a session ends.                                                                                                                                                                                                                                                                                      |
| `endOfScreenshareMessage`        | (Set through the admin interface)   | Message shown at the end of the session.                                                                                                                                                                                                                                                                                                                          |
| `enlargeCursor`                  | `false`                             | Whether to enlarge the visitor's cursor so it looks like the agent's.                                                                                                                                                                                                                                                                                             |
| `compressImages`                 | `true`                              | Whether to compress and canvas content that is sent from the visitor's browser. Set to `false` if you have high definition canvases.                                                                                                                                                                                                                              |
| `injectLookupCodeButton`         | (Set through the admin interface)   | Whether to inject a button in the lower left of the page to show the 4-digit lookup code.                                                                                                                                                                                                                                                                         |
| `lookupCodeButtonPages`          | (Set through the admin interface)   | Pages on which to show the lookup code button (e.g. `['https://site.com/help/*']`).                                                                                                                                                                                                                                                                               |
| `lookupCodeButtonStyle`          | (Set through the admin interface)   | Position of the lookup code button.                                                                                                                                                                                                                                                                                                                               |
| `lookupCodeElement`              | (Set through the admin interface)   | CSS selector or HTML element object to replace text of with 4-digit lookup code.                                                                                                                                                                                                                                                                                  |
| `lookupCodeKey`                  | `17`                                | Which keyboard key to show the lookup code with (17 is the Ctrl key).                                                                                                                                                                                                                                                                                             |
| `lookupCodeKeyRepetitions`       | `5`                                 | Number of times the visitor needs to press `lookupCodeKey` to see the lookup code.                                                                                                                                                                                                                                                                                |
| `maskedElements`                 | (Set through the admin interface)   | List of CSS selectors (e.g. `['.credit-card']`) to mask when screen sharing in addition to elements with a `no-upscope` CSS class.                                                                                                                                                                                                                                |
| `noRemoteElements`               | (Set through the admin interface)   | List of CSS selectors for elements where the agent should not have the ability to click/type.                                                                                                                                                                                                                                                                     |
| `proxyAssets`                    | (Set through the admin interface)   | List of wildcard strings (e.g. `['://localhost:/*']`) to proxy from the browser when screen sharing. This is useful to allow screen sharing in development or staging environments.                                                                                                                                                                               |
| `region`                         | (Visitor's closest region)          | Which region to connect to.                                                                                                                                                                                                                                                                                                                                       |
| `requireAuthorizationForSession` | (Set through the admin interface)   | Whether to ask for visitor authorization before screen sharing.                                                                                                                                                                                                                                                                                                   |
| `requireControlRequest`          | (Set through the admin interface)   | Whether to ask the visitor separately for remote control capabilities.                                                                                                                                                                                                                                                                                            |
| `rewriteExternalLinks`           | (Set through the admin interface)   | Whether to automatically change links to third-party websites to make use of our proxy.                                                                                                                                                                                                                                                                           |
| `sharingRoot`                    | `window`                            | A same-origin `Window` object whose document is shared instead of the current page. Useful to share only part of the page by loading that content in an iframe and passing the iframe's `contentWindow` — see [Sharing Only Part of the Page](https://cobrowsingapi.com/docs/sdk/web/installation.md). Can only be set through JavaScript.                                   |
| `showTerminateButton`            | (Set through the admin interface)   | Whether to show the visitor a "Stop session" button.                                                                                                                                                                                                                                                                                                              |
| `showAgentRequestButton`         | (Set through the admin interface)   | Whether to show the visitor the request agent button (one of `"always"`, `"when_available"`, or `"never"`).                                                                                                                                                                                                                                                       |
| `showUpscopeLink`                | `true`                              | Whether to show the Co-Browsing API link to visitors. Setting this to `false` only works if whitelabeling is included in your plan.                                                                                                                                                                                                                                    |
| `storageImplementation`          | `localStorage` + cookies            | An object that implements the [Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) interface to use as storage for all visitor data.                                                                                                                                                                                                               |
| `storageKey`                     | `null`                              | An optional string to use to scope all visitor data stored on the browser.                                                                                                                                                                                                                                                                                        |
| `screenWakeLock`                 | `true`                              | Whether to attempt to place a screen lock during sessions.                                                                                                                                                                                                                                                                                                        |
| `trackConsole`                   | (Set through the admin interface)   | Whether to track console content to display to the viewer.                                                                                                                                                                                                                                                                                                        |
| `unavailableAgentRequestRedirectTo`          | `null`                | URL to redirect the visitor to when no agents are available after requesting an agent.                                                                                                                                                                                                                                                                            |
| `unavailableAgentRequestRedirectImmediately` | `false`               | Whether to redirect immediately when no agents are available, instead of showing the unavailable message first.                                                                                                                                                                                                                                                   |
| `useFingerprinting`              | `true`                              | Whether to use a device fingerprint to recognize the device cross-domain.                                                                                                                                                                                                                                                                                         |
| `computedStyleSelectors`         | `[]`                                | A list of selectors that need to have the entire computed style sent from the browser (as the CSS won't be available to the agent). You can set to `"react"` if your whole app is built in React or `"angular"` if it's built in Angular, and we'll automatically send everything not added by React or Angular, such as elements injected by browser extensions. |

##### Messages

These settings are mostly translations. Each of them accepts either a string, or an object keyed by language code (e.g. `{ en: "Yes", it: "Si" }`) — the translation matching the visitor's browser language is shown, falling back to `en` if their language isn't included. See [Multi-Language Support](https://cobrowsingapi.com/docs/sdk/web/translations.md) for examples. All of these can also be configured per language through the dashboard.

| Option                                  | Default Value                     | Description                                                                                                                                                                                                                                |
| --------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `authorizationPromptMessage`            | (Set through the admin interface) | The text to display on the authorization prompt. `{%agentName%\|our agent}` is replaced with the name of the requesting agent (or the default `our agent` if there is no name). `{%currentDomain%}` is replaced with the current hostname. |
| `authorizationPromptTitle`              | (Set through the admin interface) | The title to display on the authorization prompt.                                                                                                                                                                                          |
| `callAudioAuthorizationFailedMessage`   | (Set through the admin interface) | Message shown when audio authorization fails during a call.                                                                                                                                                                                |
| `callAudioAuthorizationFailedTitle`     | (Set through the admin interface) | Title shown when audio authorization fails during a call.                                                                                                                                                                                  |
| `callPopupFailedMessage`                | (Set through the admin interface) | Message shown when the call popup fails.                                                                                                                                                                                                   |
| `callPopupFailedTitle`                  | (Set through the admin interface) | Title shown when the call popup fails.                                                                                                                                                                                                     |
| `callPopupNoInputMessage`               | (Set through the admin interface) | Message shown when no audio input device is found.                                                                                                                                                                                         |
| `callPopupNoInputTitle`                 | (Set through the admin interface) | Title shown when no audio input device is found.                                                                                                                                                                                           |
| `callPopupNoOutputMessage`              | (Set through the admin interface) | Message shown when no audio output device is found.                                                                                                                                                                                        |
| `callPopupNoOutputTitle`                | (Set through the admin interface) | Title shown when no audio output device is found.                                                                                                                                                                                          |
| `callPromptText`                        | (Set through the admin interface) | Message to show the visitor when an audio call is initiated.                                                                                                                                                                               |
| `controlRequestTitle`        | (Set through the admin interface) | The title to display on the control prompt.                                                                                                                                                                                                |
| `controlRequestMessage`      | (Set through the admin interface) | Message to display on the control prompt.                                                                                                                                                                                                  |
| `fullScreenRequestTitle`     | (Set through the admin interface) | The title to display on the full screen prompt.                                                                                                                                                                                            |
| `fullScreenRequestMessage`   | (Set through the admin interface) | Message to display on the full screen prompt.                                                                                                                                                                                              |                                                                                                                                                                   |
| `lookupCodeKeyMessage`       | (Set through the admin interface) | Message of prompt with the lookup code. `{%lookupCode%}` is replaced with the lookup code.                                                                                                                                                 |
| `lookupCodeKeyTitle`                    | (Set through the admin interface) | Title of prompt with the lookup code.                                                                                                                                                                                                      |
| `sessionRatingAgentLabel`               | (Set through the admin interface) | Label for the agent rating field in the session rating prompt.                                                                                                                                                                             |
| `sessionRatingFeedbackLabel`            | (Set through the admin interface) | Label for the feedback field in the session rating prompt.                                                                                                                                                                                 |
| `sessionRatingModalMessage`             | (Set through the admin interface) | Message shown in the session rating modal.                                                                                                                                                                                                 |
| `sessionRatingModalTitle`               | (Set through the admin interface) | Title of the session rating modal.                                                                                                                                                                                                         |
| `sessionRatingSessionLabel`             | (Set through the admin interface) | Label for the session rating field in the session rating prompt.                                                                                                                                                                           |
| `sessionRatingSubmitLabel`              | (Set through the admin interface) | Label for the submit button in the session rating prompt.                                                                                                                                                                                  |
| `translationsNo`                        | (Set through the admin interface) | Translation for _No_.                                                                                                                                                                                                                      |
| `translationsOk`             | (Set through the admin interface) | Translation for _Ok_.                                                                                                                                                                                                                      |
| `translationsStopSession`    | (Set through the admin interface) | Translation for _End Session_.                                                                                                                                                                                                             |
| `translationsYes`            | (Set through the admin interface) | Translation for _Yes_.                                                                                                                                                                                                                     |

##### Functions

You can pass the following functions to further customize how Co-Browsing API behaves.

| Option                              | Arguments                                  | Return Value           | Description                                                                                                                                                                                    |
| ----------------------------------- | ------------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowRemoteMiddleware`             | `(element: HTMLElement)`                   | Boolean                | Set to change the behavior of the remote control functionality. Return `true` if the agent should have control over the element, `false` if they should **not** have control.                  |
| `customCallController`              | `(cb: function)`                           | Boolean (via callback) | Set to a function to change the look of the audio call ringing view. Return `true` to accept the call, `false` to reject it.                                                                   |
| `customControlRequestController`    | `(cb: function)`                           | Boolean (via callback) | Set to a function to change the look of the control request view. Return `true` to accept the control request, `false` to reject it.                                                           |
| `customFullScreenRequestController` | `(cb: function)`                           | Boolean (via callback) | Set to a function to change the look of the full screen request view. Return `true` to accept the full screen request, `false` to reject it.                                                   |
| `maskElementMiddleware`             | `(element: HTMLElement)`                   | Boolean                | Set to change the behavior of the masking functionality. Return `true` if the element should be masked, `false` if it should **not** be masked.                                                |
| `onSessionRequest`                  | `(cb: function, requestingAgent: string?)` | Boolean (via callback) | Set to change the behavior of the visitor authorization flow. Call the callback with `true` to authorize the session, with `false` to reject it.                                               |
| `shouldComputeStyleMiddleware`      | `(element: HTMLElement)`                   | Boolean                | Set to change the behavior of whether we get the computed style of a particular element before sending it to the agent.                                                                        |
| `styleSheetContentFromRules`        | `(element: HTMLElement)`                   | Boolean                | Set to change the behavior of whether we get the content of a stylesheet from its rules, or whether we can proxy its source from the server. Useful for libraries such as `styled-components`. |

#### SDK Functions

Source: https://cobrowsingapi.com/docs/sdk/web/sdk-functions

Here's a list of all the functions supported by Co-Browsing API's SDK. These can be called with `Upscope('FUNCTION_NAME', ...args);`.

| Function Name        | Arguments                                                                                                                                                                                                                                                                                    | Description                                                                                                                                                                                                                                                                                                              |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `connect`            | —                                                                                                                                                                                                                                                                                            | Starts the socket connection (The connection starts automatically if `autoconnect` is set to `true` in the [configuration](https://cobrowsingapi.com/docs/sdk/web/configuration-options.md).) to the servers.                                                                                                     |
| `customMessage`      | `(message: Record<string, unknown>)`                                                                                                                                                                                                                                                         | Sends a custom message to all observers. Observers can listen for these messages using the `customMessage` event.                                                                                                                                                                                                         |
| `expectDisconnect`   | `({ returnTimeSeconds?: number; message?: string; title?: string; })`                                                                                                                                                                                                                        | Call this before redirecting the Visitor to a page without the SDK installed (e.g. an external log in flow) to indicate to the Agent why the Visitor is offline and that they will return shortly. Optionally set `returnTimeSeconds` to revert to the default "Visitor is not connected anymore" message after a while. |
| `fullTabEnd`         | —                                                                                                                                                                                                                                                                                            | Ends full tab sharing.                                                                                                                                                                                                                                                                                                   |
| `fullTabStart`       | —                                                                                                                                                                                                                                                                                            | Starts full tab sharing, allowing the agent to see the entire browser tab including content outside of the page where the SDK is installed.                                                                                                                                                                              |
| `getLookupCode`      | `(callback: function(code: string))`                                                                                                                                                                                                                                                         | Returns the [lookup code](https://cobrowsingapi.com/docs/sdk/the-lookup-code.md) through the callback.                                                                                                                                                                                                                                    |
| `getPublicLink`      | `({ agentId: string, agentName: string }, callback: function(publicLink: string))`                                                                                                                                                                                                           | Returns a public link for the specified agent through the callback (only for presentation sharing mode).                                                                                                                                                                                                                                                      |
| `getShortId`         | `(callback: function(shortId: string))`                                                                                                                                                                                                                                                      | Returns the Visitor's shortId through the callback.                                                                                                                                                                                                                                                                      |
| `getWatchLink`       | `(callback: function(link: string))`                                                                                                                                                                                                                                                         | Returns the watch link through the callback.                                                                                                                                                                                                                                                                             |
| `init`               | `(configuration: Partial<SDKConfiguration>)` (Full list of configuration options found [here](https://cobrowsingapi.com/docs/sdk/web/configuration-options.md).)                                                                                                                              | Used to initiate the SDK. This must be the first function called.                                                                                                                                                                                                                                                        |
| `on`                 | `(...events: string, callback: function)`                                                                                                                                                                                                                                                    | Adds a listener for the [events](https://cobrowsingapi.com/docs/sdk/web/listening-for-events.md) provided.                                                                                                                                                                                                                                    |
| `prefetchAssets`     | —                                                                                                                                                                                                                                                                                            | Call if you know a Session is about to happen to start prepping for it and make it go live faster.                                                                                                                                                                                                                       |
| `registerScreenStream` | `(stream: MediaStream)`                                                                                                                                                                                                                                                                    | Registers a pre-acquired screen capture stream to be used the next time full tab sharing starts, instead of prompting the Visitor for screen sharing permission. The stream is used once and must contain a live video track. The SDK never stops the stream's tracks: your app keeps ownership and is responsible for stopping them when done. |
| `registerVideoStream`  | `(stream: MediaStream)`                                                                                                                                                                                                                                                                    | Registers a pre-acquired camera stream to be used the next time the Visitor's camera is turned on during a video call, instead of prompting the Visitor for camera permission. The same rules as `registerScreenStream` apply: the stream is used once, must contain a live video track, and its tracks are never stopped by the SDK. |
| `reset`              | `(reopenConnection: boolean)`                                                                                                                                                                                                                                                                | Used to reset the connection and clear all identity from the Visitor. A new Visitor will be generated with a fresh ID. If `reopenConnection` is `false`, the connection will not be automatically re-opened, and the SDK will be in its initial idle state.                                                             |
| `stopRemoteControl`  | —                                                                                                                                                                                                                                                                                            | Revokes the Agent's remote control of the page during an active Session. The Agent can request control again.                                                                                                                                                                                                           |
| `stopSession`        | —                                                                                                                                                                                                                                                                                            | Terminates an active Session.                                                                                                                                                                                                                                                                                            |
| `submitRating`       | `(ratings: { userSessionRating?: 1 \| 2 \| 3 \| 4 \| 5, userAgentRating?: 1 \| 2 \| 3 \| 4 \| 5, userAgentFeedback?: string })`                                                                                                                                                              | Submits a user rating after a Session has ended.                                                                                                                                                                                                                                                                         |
| `updateConnection`   | `(updates: {uniqueId?: string, identities?: string[], tags?: string[], integrationIds?: string[], callName?: string, agentPrompt?: string, allowRemoteConsole?: boolean, allowRemoteClick?: boolean, allowRemoteScroll?: boolean, allowRemoteType?: boolean, allowAgentRedirect?: boolean})` | Used to update the identity of the visitor or settings after pageload.                                                                                                                                                                                                                                                   |

#### Listening for Events

Source: https://cobrowsingapi.com/docs/sdk/web/listening-for-events

You can listen for events by running the following code:

```javascript
Upscope('on', ...eventNames, callbackFunction);
```

##### List of Events

| Event Name               | Additional Arguments                                                                                                         | Description                                                                                          |
|--------------------------|------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
| `callAccepted`           | —                                                                                                                            | An audio call has been accepted by the visitor.                                                     |
| `callEnd`                | —                                                                                                                            | An audio call has ended.                                                                             |
| `callStart`              | —                                                                                                                            | An audio call has started.                                                                           |
| `connection`             | —                                                                                                                            | The connection has been established with the servers.                                         |
| `connectionReset`        | —                                                                                                                            | The connection has been reset, and a new visitor will be created.                                   |
| `newObserver`            | `observerId: string, observerData: { id: string; name: string \| null; screenWidth: number; screenHeight: number; windowWidth: number; windowHeight: number; hasFocus: boolean;}` | Indicates a new agent is observing.                                                                 |
| `observerUpdate`         | `observerId: string, observerData: Partial<{ id: string; name: string \| null; screenWidth: number; screenHeight: number; windowWidth: number; windowHeight: number; hasFocus: boolean;}>` | An observer's data has changed.                                                                      |
| `observerGone`           | `observerId: string`                                                                                                        | `observerId` is no longer observing.                                                                 |
| `observerContentVisible`  | `observerId: string`                                                                                                        | `observerId` can now see the content (it is no longer loading).                                     |
| `observersCount`         | `count: number`                                                                                                             | Provides an accurate tally of the number of people currently observing.                              |
| `sessionEnd`             | —                                                                                                                            | A session has ended.                                                                                 |
| `sessionRequest`         | —                                                                                                                            | An agent is asking to start a session.  (If you want to change the authorization flow, you'll need to add a `onSessionRequest` function to the [configuration](https://cobrowsingapi.com/docs/sdk/web/configuration-options.md).) |
| `sessionContinue`        | —                                                                                                                            | A session is continuing from a previous pageview.                                                  |
| `sessionStart`           | —                                                                                                                            | A session has started.                                                                                |
| `customMessage`          | `sender: { observer: string } \| { visitor: string }, message: Record<string, unknown>`                                    | A custom message sent by an observer or visitor.                                                     |

##### Tracking Event Data Such as Clicks

All events originating from the agent will have a `isUpscopeBrowserInstruction` attribute set to `true`.

```javascript
button.addEventListener("click", evt => {
  console.log("Clicked by ", evt.isUpscopeBrowserInstruction ? "agent" : "user")
});
```

#### Multi-Language Support

Source: https://cobrowsingapi.com/docs/sdk/web/translations

Co-Browsing API supports custom translations for all user-facing text. You can configure them per language through the dashboard, or set them during initialization.

##### Basic Translation Setup

Pass translation strings directly to the `init` function:

```javascript
Upscope("init", {
  authorizationPromptTitle: "Co-Browsing request",
  authorizationPromptMessage: "Would you like to let {%agentName%|our agent} co-browse with you?",
  translationsYes: "Yes",
  translationsNo: "No",
});
```

##### Multi-Language Setup

Instead of a string, every text option also accepts an object keyed by language code. Co-Browsing API automatically shows the translation matching the visitor's browser language, falling back to `en` if their language isn't included:

```javascript
Upscope("init", {
  authorizationPromptTitle: {
    en: "Co-Browsing request",
    fr: "Demande de co-navigation",
    es: "Solicitud de co-navegación",
  },
  authorizationPromptMessage: {
    en: "Would you like to let {%agentName%|our agent} co-browse with you?",
    fr: "Souhaitez-vous permettre à {%agentName%|notre agent} de co-naviguer avec vous ?",
    es: "¿Le gustaría permitir que {%agentName%|nuestro agente} co-navegue con usted?",
  },
  translationsYes: { en: "Yes", fr: "Oui", es: "Sí" },
  translationsNo: { en: "No", fr: "Non", es: "No" },
});
```

You can also configure all of these translations per language through the dashboard, without any code changes.

##### Available Translation Keys

See the [Messages section of Configuration Options](https://cobrowsingapi.com/docs/sdk/web/configuration-options.md#messages) for a complete list of translatable strings.

### iOS SDK

Source: https://cobrowsingapi.com/docs/sdk/ios

**Beta:**
The mobile SDKs are currently in beta. If you encounter any issues, please [contact us](mailto:team@cobrowsingapi.com).

The Co-Browsing API iOS SDK allows you to integrate screen sharing capabilities into your iOS app. It supports both SwiftUI and UIKit, with features for element redaction, visitor identification, and session management.

#### Requirements

- iOS 14.0+
- Swift 5.9+
- Xcode 15.0+

#### Features

- **Screen sharing**: Allow agents to view your app's screen in real-time
- **Element redaction**: Hide sensitive information during screen sharing
- **Visitor identification**: Identify users and link sessions to your CRM
- **Lookup codes**: Generate 4-digit codes for easy session joining
- **iPad support**: Automatic handling of split-screen scenarios

#### Installation

Source: https://cobrowsingapi.com/docs/sdk/ios/installation

**Beta:**
The mobile SDKs are currently in beta. If you encounter any issues, please [contact us](mailto:team@cobrowsingapi.com).

##### Requirements

- iOS 14.0+
- Swift 5.9+
- Xcode 15.0+

##### Installation

**Swift Package Manager:**

Add the package dependency to your `Package.swift`:

```swift
dependencies: [
    .package(url: "https://github.com/upscopeio/cobrowsing-ios.git", from: "2026.8.3")
]
```

Or add it through Xcode:
1. Go to **File** > **Add Package Dependencies**
2. Enter `https://github.com/upscopeio/cobrowsing-ios.git`
3. Select the version and add to your target

**CocoaPods:**

Add the following to your `Podfile`:

```ruby
pod 'UpscopeSDK', '~> 2026.8.3'
```

Then run:

```bash
pod install
```

##### Initialization

Initialize the SDK in your `AppDelegate` or `App` struct:

```swift
import UpscopeSDK

let config = UpscopeConfiguration(apiKey: "YOUR_API_KEY")
try Upscope.shared.initialize(with: config)
```

The SDK auto-connects by default. To disable this, set `autoconnect: false` in the configuration and call `Upscope.shared.connect()` manually when ready.

###### SwiftUI App Example

```swift
import SwiftUI
import UpscopeSDK

@main
struct MyApp: App {
    init() {
        let config = UpscopeConfiguration(apiKey: "YOUR_API_KEY")
        try? Upscope.shared.initialize(with: config)
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
```

###### UIKit AppDelegate Example

```swift
import UIKit
import UpscopeSDK

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let config = UpscopeConfiguration(apiKey: "YOUR_API_KEY")
        try? Upscope.shared.initialize(with: config)
        return true
    }
}
```

##### Public API Key

You can find your public API key in the installation page of your Co-Browsing API dashboard.

##### iPad Split Screen Support

The SDK automatically handles iPad split screen scenarios. Each app instance captures only its own portion of the screen, and alerts appear on the correct window. No additional configuration is required.

##### Lookup Code on Shake

By default, shaking the device will display the lookup code in an alert. This can be disabled via configuration options.

#### Configuration Options

Source: https://cobrowsingapi.com/docs/sdk/ios/configuration-options

You can customize the behavior of the Co-Browsing API iOS SDK through configuration options.

##### Setting Configuration

Pass options when creating the `UpscopeConfiguration`:

```swift
let config = UpscopeConfiguration(
    apiKey: "YOUR_API_KEY",
    requireAuthorizationForSession: true,
    authorizationPromptTitle: "Screen Sharing Request",
    authorizationPromptMessage: "Allow {%agentName%|Support} to view your screen?",
    endOfSessionMessage: "Thanks for using screen sharing!",
    translationsYes: "Allow",
    translationsNo: "Decline"
)

try Upscope.shared.initialize(with: config)
```

##### Configuration Options

Each option resolves in this order: a value you pass here overrides the matching dashboard setting, which overrides the SDK's built-in default (shown in the **Default** column).

###### Session Authorization

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `requireAuthorizationForSession` | `Bool?` | `true` | Require user permission before screen sharing starts. Resolved from the value set here, else the team's dashboard setting, else `true`. When it resolves `false`, sessions start silently and `onSessionRequest` is not called. |
| `authorizationPromptTitle` | `String?` | (Set through the admin interface) | Custom title for the authorization dialog. |
| `authorizationPromptMessage` | `String?` | (Set through the admin interface) | Custom message for the authorization dialog. Supports placeholders. |

###### Message Placeholders

The `authorizationPromptMessage` supports these placeholders:
- `{%agentName%|fallback}` - Agent's name with a fallback if unavailable
- `{%currentDomain%}` - App name on iOS

Example:
```swift
authorizationPromptMessage: "{%agentName%|Our support team} would like to view your screen"
```

###### UI Display

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `showTerminateButton` | `Bool?` | (Set through the admin interface) | Show a button to end the screen sharing session. |
| `showUpscopeLink` | `Bool?` | `true` | Show the Co-Browsing API link to the user. Setting this to `false` only works if whitelabeling is included in your plan. |
| `endOfSessionMessage` | `String?` | (Set through the admin interface) | Message displayed when the session ends. |
| `stopSessionText` | `String?` | (Set through the admin interface) | Custom text for the stop session button. |

###### Remote Control

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `allowRemoteClick` | `Bool?` | (Set through the admin interface) | Allow agents to remotely tap on the screen. |
| `allowRemoteScroll` | `Bool?` | (Set through the admin interface) | Allow agents to remotely scroll the screen. |
| `requireControlRequest` | `Bool?` | `false` | Require user approval before agents can use remote input. Resolved from the value set here, else the team's dashboard setting, else `false`. When it resolves `false`, remote input is granted without a separate control request. |
| `controlRequestTitle` | `String?` | (Set through the admin interface) | Custom title for the control request prompt. |
| `controlRequestMessage` | `String?` | (Set through the admin interface) | Custom message for the control request prompt. |
| `onControlRequest` | `((SessionRequestResponse, String?) -> Cancellable?)?` | (Custom UI) | Called when an agent requests remote control. Only invoked when `requireControlRequest` is enabled (which defaults to `false`). The closure receives the response and the requesting agent's name (may be `nil`). Show your own UI, then call `response.accept()` or `response.reject()`. When unset, the SDK shows its default control request prompt. See [Custom Authorization UI](#custom-authorization-ui). |

###### Lookup Code

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enableLookupCodeOnShake` | `Bool?` | (Set through the admin interface) | Show lookup code popup when device is shaken. |
| `lookupCodeKeyTitle` | `String?` | (Set through the admin interface) | Custom title for the shake detection alert. |
| `lookupCodeKeyMessage` | `String?` | (Set through the admin interface) | Custom message for shake alert. Supports `{%lookupCode%}` placeholder. |

###### Localization Strings

| Option | Type | Description |
|--------|------|-------------|
| `translationsYes` | `String?` | Custom text for "Allow" button in authorization prompt. |
| `translationsNo` | `String?` | Custom text for "Deny" button in authorization prompt. |
| `translationsOk` | `String?` | Custom text for "OK" button. |

###### Multi-Language Translations

Every text option (titles, messages, and the strings above) also accepts a dictionary keyed by language code instead of a single string. The translation matching the device language is shown, falling back to `en` if the device language isn't included:

```swift
translationsYes: ["en": "Yes", "it": "Si"],
translationsNo: ["en": "No", "it": "No"]
```

All of these can also be configured per language through the dashboard.

###### Full Device Screen Sharing

| Option | Type | Description |
|--------|------|-------------|
| `allowFullScreen` | `Bool?` | Allow agents to request full device screen sharing during sessions. Also requires the setup described in [Full Device Screen Sharing](https://cobrowsingapi.com/docs/sdk/ios/full-device-screen-sharing.md). Default: (Set through the admin interface). |
| `disableFullScreenWhenMasked` | `Bool?` | When `true`, full device screen sharing is automatically declined if any masked views are present. Default: (Set through the admin interface). |
| `onFullDeviceRequest` | `((SessionRequestResponse, String?) -> Cancellable?)?` | Called when an agent requests full-device sharing, before the system broadcast picker appears. The closure receives the response and the requesting agent's name (may be `nil`). Show your own UI, then call `response.accept()` to continue to the picker or `response.reject()` to decline. When unset, the SDK proceeds to the picker directly. |

See [Full Device Screen Sharing](https://cobrowsingapi.com/docs/sdk/ios/full-device-screen-sharing.md) for the full setup guide.

###### System Options

| Option | Type | Description |
|--------|------|-------------|
| `autoconnect` | `Bool?` | Automatically connect on initialization. Default: `true` (set through the admin interface). |
| `region` | `String?` | Server region for connections. |
| `onPremiseBaseEndpoint` | `String?` | The base endpoint of your [on-premise deployment](https://cobrowsingapi.com/docs/on-premise/quick-start.md) (your instance's `BASE_ENDPOINT`), e.g. `"https://cobrowsing.acmetech.com"`. When set, the SDK connects to your instance instead of the cloud servers, and `region` is ignored. |
| `webviewMaskedElements` | `[String]?` | List of CSS selectors (e.g. `[".credit-card"]`) to redact inside WebViews enrolled with `redactWebView`. Merged with the dashboard **Masked elements** setting and with selectors passed to `redactWebView`; a local list never disables dashboard masking. |

##### Custom Authorization UI

`onSessionRequest` is only invoked when `requireAuthorizationForSession` resolves `true` — when authorization is disabled (locally or via the team's dashboard setting), sessions start without any prompt and the handler is never called.

You can replace the default authorization dialog with your own UI by setting the `onSessionRequest` property after creating the configuration:

```swift
var config = UpscopeConfiguration(
    apiKey: "YOUR_API_KEY",
    requireAuthorizationForSession: true
)

config.onSessionRequest = { response, agentName in
    // Show your custom UI here
    // Call response.accept() or response.reject()
    myCustomAlert.show(agentName: agentName) { accepted in
        if accepted {
            response.accept()
        } else {
            response.reject()
        }
    }
    // Return a Cancellable for cleanup if the request is dismissed externally
    return Cancellable {
        myCustomAlert.dismiss()
    }
}

try Upscope.shared.initialize(with: config)
```

Similarly, use `onControlRequest` to customize the remote control authorization prompt:

```swift
var config = UpscopeConfiguration(
    apiKey: "YOUR_API_KEY",
    requireControlRequest: true
)

config.onControlRequest = { response, agentName in
    // Show your custom UI, e.g. "{agentName} wants to control your screen"
    myControlAlert.show(agentName: agentName) { accepted in
        if accepted {
            response.accept()
        } else {
            response.reject()
        }
    }
    return nil // or return a Cancellable
}
```

Use `onFullDeviceRequest` to intercept full-device sharing requests before the system broadcast picker appears:

```swift
var config = UpscopeConfiguration(
    apiKey: "YOUR_API_KEY"
)

config.onFullDeviceRequest = { response, agentName in
    // Show your custom UI before the system broadcast picker
    myCustomAlert.show(agentName: agentName) { accepted in
        if accepted {
            response.accept() // proceeds to the system broadcast picker
        } else {
            response.reject() // declines without showing the picker
        }
    }
    // Return a Cancellable called if the request is dismissed externally
    return Cancellable {
        myCustomAlert.dismiss()
    }
}

try Upscope.shared.initialize(with: config)
```

##### Full Example

```swift
let config = UpscopeConfiguration(
    apiKey: "YOUR_API_KEY",
    requireAuthorizationForSession: true,
    authorizationPromptTitle: "Screen Share",
    authorizationPromptMessage: "{%agentName%|Support} wants to help you",
    showTerminateButton: true,
    endOfSessionMessage: "Session ended. Thank you!",
    stopSessionText: "End Session",
    allowRemoteClick: true,
    allowRemoteScroll: true,
    enableLookupCodeOnShake: true,
    lookupCodeKeyTitle: "Your Code",
    lookupCodeKeyMessage: "Share this code: {%lookupCode%}",
    translationsYes: "Yes, share",
    translationsNo: "No thanks",
    translationsOk: "Got it",
    region: "us-east"
)

try Upscope.shared.initialize(with: config)
```

#### SDK Functions

Source: https://cobrowsingapi.com/docs/sdk/ios/sdk-functions

Here's a list of all the functions and properties supported by the Co-Browsing API iOS SDK.

All methods and properties are accessed through the `Upscope.shared` singleton.

##### Connection Management

| Function | Description |
|----------|-------------|
| `connect()` | Establishes a WebSocket connection to the servers. |
| `disconnect()` | Closes the connection and ends any active session. |
| `reset(reconnect: Bool = true)` | Resets the connection, clearing all stored identities and visitor data. Pass `false` to stay disconnected after reset. |

##### Session Control

| Function | Description |
|----------|-------------|
| `stopSession()` | Ends the current screen sharing session. |
| `requestAgent()` | Signals that the visitor wants assistance from an agent. |
| `cancelAgentRequest()` | Cancels a pending agent request. |
| `getLookupCode()` | Requests a 4-digit lookup code from the server. Access the code via the `lookupCode` property or `lookupCodePublisher`. |
| `sendCustomMessage(_ message: String)` | Sends a custom text or JSON message to the agent (max 5000 characters). |
| `stopRemoteControl()` | Revokes the agent's remote control of the device. The session continues; only the agent's ability to interact stops. Safe no-op if no agent has control. |
| `stopFullDeviceSharing()` | Stops full-device screen sharing and reverts to in-app screen sharing. Safe no-op if not active. |

##### State Properties

| Property | Type | Description |
|----------|------|-------------|
| `isConnected` | `Bool` | Whether the SDK is currently connected to the server. |
| `isInSession` | `Bool` | Whether a screen sharing session is currently active. |
| `connectionState` | `ConnectionState` | Current connection state (`.inactive`, `.connecting`, `.connected`, `.reconnecting`, `.error`). |
| `sessionState` | `SessionState` | Current session state (`.inactive`, `.pendingRequest`, `.active`, `.paused`, `.ended`). |
| `shortId` | `String?` | The visitor's unique short ID assigned by the server. |
| `lookupCode` | `String?` | The current 4-digit lookup code, if one has been generated. |
| `watchLink` | `URL?` | The full URL where agents can view the session (`https://upscope.com/w/{shortId}`). |
| `remoteControlState` | `RemoteControlState` | Current remote control state (`.inactive`, `.pendingRequest`, `.active`). |
| `fullDeviceSharingState` | `FullDeviceSharingState` | Current full-device sharing state (`.inactive`, `.pendingRequest`, `.active`). |

##### Visitor Identification

You can set visitor identity either through direct property assignment or as a batch update.

###### Direct Properties

```swift
Upscope.shared.uniqueId = "user-123"
Upscope.shared.callName = "John Smith"
Upscope.shared.tags = ["#VIP"]
Upscope.shared.identities = ["John Smith", "john@example.com"]
Upscope.shared.metadata = ["plan": "enterprise", "region": "US"]
```

###### Batch Update

Use `updateConnection()` to update multiple fields at once:

```swift
Upscope.shared.updateConnection(
    uniqueId: "user-123",
    callName: "John Smith",
    tags: ["#VIP"],
    identities: ["John Smith", "john@example.com"],
    metadata: ["plan": "enterprise", "region": "US"]
)
```

Pass `nil` to keep an existing value unchanged. Only non-nil parameters are updated.

##### Reactive State (Combine)

Subscribe to state changes using Combine publishers:

```swift
import Combine

var cancellables = Set<AnyCancellable>()

// Connection state changes
Upscope.shared.connectionStatePublisher
    .sink { state in
        print("Connection: \(state)")
    }
    .store(in: &cancellables)

// Session state changes
Upscope.shared.sessionStatePublisher
    .sink { state in
        print("Session: \(state)")
    }
    .store(in: &cancellables)

// Short ID changes
Upscope.shared.shortIdPublisher
    .sink { shortId in
        print("Short ID: \(shortId ?? "none")")
    }
    .store(in: &cancellables)

// Lookup code changes
Upscope.shared.lookupCodePublisher
    .sink { code in
        print("Lookup code: \(code ?? "none")")
    }
    .store(in: &cancellables)

// Session mode changes
Upscope.shared.sessionModePublisher
    .sink { mode in
        print("Session mode: \(String(describing: mode))")
    }
    .store(in: &cancellables)

// Remote control state changes
Upscope.shared.remoteControlStatePublisher
    .sink { state in
        print("Remote control: \(state)")
    }
    .store(in: &cancellables)

// Full device sharing state changes
Upscope.shared.fullDeviceSharingStatePublisher
    .sink { state in
        print("Full device sharing: \(state)")
    }
    .store(in: &cancellables)
```

##### Masking

Hide sensitive content from agents during screen sharing.

| Property/Function | Description |
|-------------------|-------------|
| `maskSecureTextFields` | `Bool` — Automatically mask secure text fields. Default: `true`. |
| `addMaskedView(_ view: UIView)` | Register a UIKit view to be masked (hidden from agent). |
| `removeMaskedView(_ view: UIView)` | Unregister a masked view. |
| `allMaskedViews` | `[UIView]` — All currently masked views. |

###### Example

```swift
// Mask a specific UIKit view
Upscope.shared.addMaskedView(creditCardField)

// Later, remove the mask
Upscope.shared.removeMaskedView(creditCardField)

// Disable automatic secure field masking
Upscope.shared.maskSecureTextFields = false
```

#### Listening for Events

Source: https://cobrowsingapi.com/docs/sdk/ios/listening-for-events

You can listen for SDK events by implementing the `UpscopeDelegate` protocol and assigning it to the shared instance:

```swift
Upscope.shared.delegate = self
```

##### UpscopeDelegate Protocol

All delegate methods are optional.

```swift
extension YourClass: UpscopeDelegate {
    func upscope(_ upscope: Upscope, didChangeConnectionState state: ConnectionState) {
        // Connection state changed
        switch state {
        case .inactive:
            print("Inactive")
        case .connecting:
            print("Connecting...")
        case .connected:
            print("Connected")
        case .reconnecting:
            print("Reconnecting...")
        case .error(let error):
            print("Error: \(error.message)")
        }
    }

    func upscopeSessionDidStart(_ upscope: Upscope, agentName: String?) {
        print("Session started with \(agentName ?? "an agent")")
    }

    func upscopeSessionDidEnd(_ upscope: Upscope, reason: SessionEndReason) {
        switch reason {
        case .userStopped:
            print("User ended session")
        case .agentStopped:
            print("Agent ended session")
        case .timeout:
            print("Session timed out")
        case .error(let error):
            print("Session error: \(error.message)")
        }
    }

    func upscope(_ upscope: Upscope, didReceiveCustomMessage message: String, from viewerId: String) {
        print("Message from \(viewerId): \(message)")
    }

    func upscope(_ upscope: Upscope, didEncounterError error: UpscopeError) {
        print("Error: \(error.code) - \(error.message)")
    }

    func upscope(_ upscope: Upscope, viewerDidJoin viewer: Viewer) {
        print("Viewer joined: \(viewer.name ?? viewer.id)")
    }

    func upscope(_ upscope: Upscope, viewerDidLeave viewerId: String) {
        print("Viewer left: \(viewerId)")
    }

    func upscope(_ upscope: Upscope, viewerCountDidChange count: Int) {
        print("Viewers: \(count)")
    }

    func upscope(_ upscope: Upscope, didChangeRemoteControlState state: RemoteControlState) {
        // Whether an agent currently has remote control changed
        print("Remote control state: \(state)")
    }

    func upscope(_ upscope: Upscope, didChangeFullDeviceSharingState state: FullDeviceSharingState) {
        // Whether full-device screen sharing is active changed
        print("Full device sharing state: \(state)")
    }
}
```

##### Event Reference

| Method | Description |
|--------|-------------|
| `upscope(_:didChangeConnectionState:)` | Called when the connection state changes. |
| `upscopeSessionDidStart(_:agentName:)` | A screen sharing session has started. `agentName` is the agent's display name if available. |
| `upscopeSessionDidEnd(_:reason:)` | A session has ended. `reason` indicates why (user stopped, agent stopped, timeout, or error). |
| `upscope(_:didReceiveCustomMessage:from:)` | A custom message was received from a viewer. |
| `upscope(_:didEncounterError:)` | An SDK error occurred. |
| `upscope(_:viewerDidJoin:)` | An agent started viewing the session. The `Viewer` includes `id`, `name`, screen dimensions, and focus state. |
| `upscope(_:viewerDidLeave:)` | An agent stopped viewing the session. |
| `upscope(_:viewerCountDidChange:)` | The total number of active viewers changed. |
| `upscope(_:didChangeRemoteControlState:)` | Whether an agent currently has remote control of the device (ability to tap and scroll) changed. Independent of the session being active. |
| `upscope(_:didChangeFullDeviceSharingState:)` | Whether full-device (entire screen) sharing is currently running changed, as opposed to default in-app screen sharing. |

#### Full Device Screen Sharing

Source: https://cobrowsingapi.com/docs/sdk/ios/full-device-screen-sharing

By default, the Co-Browsing API iOS SDK shares only your app's screen. With full device screen sharing, agents can see the entire device screen, including other apps, the home screen, and system UI. This uses Apple's Broadcast Upload Extension (ReplayKit).

**Physical devices only:**
Full device screen sharing only works on physical devices. It is not supported on the iOS Simulator.

##### How It Works

Full device screen sharing uses a **Broadcast Upload Extension** — a separate target in your Xcode project that captures the entire screen via ReplayKit. The extension sends frames to your main app through an App Group, and the SDK transmits them to the agent over the existing connection.

When an agent requests full device mode, the SDK shows the system broadcast picker. The user taps "Start Broadcast" to begin sharing, and can stop at any time from Control Center.

##### Setup

Full device screen sharing must also be **enabled in the Co-Browsing API dashboard** (admin interface) in addition to the steps below. If it is disabled there, agents will not see the full device option during sessions.

###### 1. Add the Broadcast Extension dependency

The extension links a separate, lightweight module so your app binary never pulls in the ReplayKit capture code. Add it to your Broadcast Extension target (created in step 2) — separately from the main `UpscopeSDK` dependency on your app target.

**Swift Package Manager:**

Add the `UpscopeBroadcastExtension` product (from the same `UpscopeSDK` package you already added) to your Broadcast Extension target.

**CocoaPods:**

Add the `BroadcastExtension` subspec to your extension target in the `Podfile`:

```ruby
target 'YourAppBroadcast' do
  pod 'UpscopeSDK/BroadcastExtension', '~> 2026.8.3'
end
```

###### 2. Create the Broadcast Upload Extension target

1. In Xcode, go to **File > New > Target**
2. Select **Broadcast Upload Extension**
3. Uncheck "Include UI Extension"
4. Name it (e.g., `YourAppBroadcast`)
5. Note the bundle identifier — it must be prefixed with your main app's bundle identifier (e.g., `com.yourcompany.yourapp.broadcast`)

###### 3. Configure App Groups

Both your main app and the Broadcast Extension need to share data through an App Group:

1. Select your **main app target** > Signing & Capabilities > **+ Capability** > **App Groups**
2. Add a group identifier (e.g., `group.com.yourcompany.yourapp`)
3. Select your **extension target** > Signing & Capabilities > **+ Capability** > **App Groups**
4. Add the **same** group identifier

###### 4. Configure the Extension's Info.plist

Add the App Group identifier to the extension's `Info.plist`:

```xml
<key>UpscopeAppGroupId</key>
<string>group.com.yourcompany.yourapp</string>
```

###### 5. Implement the Extension

Replace the contents of your extension's `SampleHandler.swift` with:

```swift
import UpscopeBroadcastExtension

class SampleHandler: UpscopeSampleHandler {}
```

That's it — all the frame capture and forwarding logic is handled by `UpscopeSampleHandler`.

###### 6. Configure your app's Info.plist

Add the App Group and extension bundle identifiers to your **app's** `Info.plist` (`UpscopeAppGroupId` is the same key you added to the extension's `Info.plist` in step 4):

```xml
<key>UpscopeAppGroupId</key>
<string>group.com.yourcompany.yourapp</string>
<key>UpscopeBroadcastExtensionBundleId</key>
<string>com.yourcompany.yourapp.broadcast</string>
```

No code changes are needed — the SDK picks these up automatically.

##### How agents trigger full device mode

Once configured, agents can switch to full device mode from the Co-Browsing API dashboard during an active session. When they do:

1. The system broadcast picker appears in your app
2. The user taps **Start Broadcast**
3. The agent sees the full device screen
4. The user can stop sharing at any time from **Control Center**

If the user stops the broadcast, the SDK notifies the server automatically. The agent can also switch back to app-only mode at any time.

##### Custom request UI

By default, the system broadcast picker appears immediately when an agent requests full-device mode. To show your own confirmation UI first, set `onFullDeviceRequest` on the configuration:

```swift
config.onFullDeviceRequest = { response, agentName in
    myAlert.show(agentName: agentName) { accepted in
        if accepted {
            response.accept() // continues to the system broadcast picker
        } else {
            response.reject() // declines without showing the picker
        }
    }
    return Cancellable { myAlert.dismiss() }
}
```

See [Configuration Options](https://cobrowsingapi.com/docs/sdk/ios/configuration-options.md) for full details.

##### Limitations

- Only works on **physical devices** (not the Simulator)
- **Element masking** is not available in full device mode (the SDK cannot inspect views outside your app)
- **Remote control** (tap/scroll) only works within your app, not on the home screen or other apps
- **Drawing annotations** are not displayed in full device mode
- The user must explicitly start the broadcast via the system picker — it cannot be started programmatically

##### Without full device support

If the `UpscopeAppGroupId` key is missing from your app's `Info.plist`, the SDK will not advertise full device support. If an agent attempts full device mode, the user will see an "unsupported" message and the mode will revert automatically.

### Android SDK

Source: https://cobrowsingapi.com/docs/sdk/android

**Beta:**
The mobile SDKs are currently in beta. If you encounter any issues, please [contact us](mailto:team@cobrowsingapi.com).

The Co-Browsing API Android SDK allows you to integrate screen sharing capabilities into your Android app. It supports both Jetpack Compose and traditional View-based layouts, with features for element redaction, visitor identification, and session management.

#### Requirements

- Minimum Android API: 26 (Android 8.0)
- Target Android API: 36 or higher
- Kotlin: 1.9+
- Gradle: 8.2+
- JDK: 17+

#### Features

- **Screen sharing**: Allow agents to view your app's screen in real-time
- **Element redaction**: Hide sensitive information during screen sharing
- **Visitor identification**: Identify users and link sessions to your CRM
- **Lookup codes**: Generate 4-digit codes for easy session joining
- **Full device screen sharing**: Optionally capture the entire device screen via MediaProjection
- **No runtime permissions for in-app capture**: In-app screen sharing works without any user prompts

#### Installation

Source: https://cobrowsingapi.com/docs/sdk/android/installation

**Beta:**
The mobile SDKs are currently in beta. If you encounter any issues, please [contact us](mailto:team@cobrowsingapi.com).

##### Requirements

- Android API 26+ (Android 8.0 Oreo)
- Kotlin **2.1.0+** (projects on Kotlin 2.0.x or older will encounter metadata build failures at compile time)
- Gradle 8.2+
- JDK 17+

##### Installation

Add the dependency to your app's `build.gradle`:

**Kotlin DSL:**

```kotlin
dependencies {
    implementation("io.github.upscopeio:upscope-android-sdk:2026.8.3")
}
```

**Groovy:**

```groovy
dependencies {
    implementation 'io.github.upscopeio:upscope-android-sdk:2026.8.3'
}
```

##### Initialization

Initialize the SDK in your `Application` class:

```kotlin
import io.upscope.sdk.Upscope
import io.upscope.sdk.UpscopeConfiguration

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        val config = UpscopeConfiguration.Builder("YOUR_API_KEY")
            .build()

        Upscope.initialize(this, config)
    }
}
```

The SDK auto-connects by default. To disable this, call `.autoConnect(false)` on the builder and call `Upscope.connect()` manually when ready.

The SDK automatically binds to the current activity via lifecycle callbacks. No manual activity binding is needed.

###### Jetpack Compose Example

```kotlin
import io.upscope.sdk.Upscope
import io.upscope.sdk.UpscopeConfiguration

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        val config = UpscopeConfiguration.Builder("YOUR_API_KEY")
            .build()

        Upscope.initialize(this, config)
    }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}
```

##### Public API Key

You can find your public API key in the installation page of your Co-Browsing API dashboard.

##### Required Permissions

The SDK declares these permissions (automatically merged into your manifest):
- `INTERNET` — WebSocket connection to servers
- `ACCESS_NETWORK_STATE` — Network connectivity checks

No special permissions are required for in-app screen capture.

[Full device screen sharing](https://cobrowsingapi.com/docs/sdk/android/full-device-screen-sharing.md) must be enabled in the Co-Browsing API dashboard and also requires adding two extra permissions to your `AndroidManifest.xml`. Without them, the SDK automatically disables full device screen sharing. See the [full device screen sharing guide](https://cobrowsingapi.com/docs/sdk/android/full-device-screen-sharing.md) for details.

##### Lookup Code on Shake

By default, shaking the device will display the lookup code in a dialog. This can be disabled via configuration options.

#### Configuration Options

Source: https://cobrowsingapi.com/docs/sdk/android/configuration-options

You can customize the behavior of the Co-Browsing API Android SDK through configuration options.

##### Setting Configuration

Use the `UpscopeConfiguration.Builder` when initializing the SDK:

```kotlin
val config = UpscopeConfiguration.Builder("YOUR_API_KEY")
    .requireAuthorizationForSession(true)
    .authorizationPromptTitle("Screen Sharing Request")
    .authorizationPromptMessage("Allow {%agentName%|Support} to view your screen?")
    .endOfSessionMessage("Thanks for using screen sharing!")
    .translationsYes("Allow")
    .translationsNo("Decline")
    .build()

Upscope.initialize(applicationContext, config)
```

##### Configuration Options

Each option resolves in this order: a value you pass here overrides the matching dashboard setting, which overrides the SDK's built-in default (shown in the **Default** column).

###### Session Authorization

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `requireAuthorizationForSession` | `Boolean` | `true` | Require user permission before screen sharing starts. Resolved from the value set here, else the team's dashboard setting, else `true`. When it resolves `false`, sessions start silently and `onSessionRequest` is not called. |
| `authorizationPromptTitle` | `String` | (Set through the admin interface) | Custom title for the authorization dialog. |
| `authorizationPromptMessage` | `String` | (Set through the admin interface) | Custom message for the authorization dialog. Supports placeholders. |

###### Message Placeholders

The `authorizationPromptMessage` supports these placeholders:
- `{%agentName%|fallback}` - Agent's name with a fallback if unavailable
- `{%currentDomain%}` - App name on Android

Example:
```kotlin
.authorizationPromptMessage("{%agentName%|Our support team} would like to view your screen")
```

###### UI Display

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `showTerminateButton` | `Boolean` | (Set through the admin interface) | Show a button to end the screen sharing session. |
| `showUpscopeLink` | `Boolean` | `true` | Show the Co-Browsing API link to the user. Setting this to `false` only works if whitelabeling is included in your plan. |
| `endOfSessionMessage` | `String` | (Set through the admin interface) | Message displayed when the session ends. |
| `stopSessionText` | `String` | (Set through the admin interface) | Custom text for the stop session button. |

###### Remote Control

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `allowRemoteClick` | `Boolean` | (Set through the admin interface) | Allow agents to remotely tap on the screen. |
| `allowRemoteScroll` | `Boolean` | (Set through the admin interface) | Allow agents to remotely scroll the screen. |
| `requireControlRequest` | `Boolean` | `false` | Require user approval before agents can use remote input. Resolved from the value set here, else the team's dashboard setting, else `false`. When it resolves `false`, remote input is granted without a separate control request. |
| `controlRequestTitle` | `String` | (Set through the admin interface) | Custom title for the control request prompt. |
| `controlRequestMessage` | `String` | (Set through the admin interface) | Custom message for the control request prompt. |

###### Lookup Code

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enableLookupCodeOnShake` | `Boolean` | (Set through the admin interface) | Show lookup code dialog when device is shaken. |
| `lookupCodeKeyTitle` | `String` | (Set through the admin interface) | Custom title for the shake detection dialog. |
| `lookupCodeKeyMessage` | `String` | (Set through the admin interface) | Custom message for shake dialog. Supports `{%lookupCode%}` placeholder. |

###### Localization Strings

| Option | Type | Description |
|--------|------|-------------|
| `translationsYes` | `String` | Custom text for "Allow" button in authorization prompt. |
| `translationsNo` | `String` | Custom text for "Deny" button in authorization prompt. |
| `translationsOk` | `String` | Custom text for "OK" button. |

###### Multi-Language Translations

Every text option (titles, messages, and the strings above) also accepts a map keyed by language code instead of a single string. The translation matching the device language is shown, falling back to `en` if the device language isn't included:

```kotlin
.translationsYes(mapOf("en" to "Yes", "it" to "Si"))
.translationsNo(mapOf("en" to "No", "it" to "No"))
```

All of these can also be configured per language through the dashboard.

###### System Options

| Option | Type | Description |
|--------|------|-------------|
| `autoConnect` | `Boolean` | Automatically connect on initialization. Default: `true` (set through the admin interface). |
| `region` | `String` | Server region for connections. |
| `onPremiseBaseEndpoint` | `String` | The base endpoint of your [on-premise deployment](https://cobrowsingapi.com/docs/on-premise/quick-start.md) (your instance's `BASE_ENDPOINT`), e.g. `"https://cobrowsing.acmetech.com"`. When set, the SDK connects to your instance instead of the cloud servers, and `region` is ignored. |
| `allowFullScreen` | `Boolean` | Allow agents to request full device screen sharing during sessions. Also requires the setup described in [Full Device Screen Sharing](https://cobrowsingapi.com/docs/sdk/android/full-device-screen-sharing.md). (Set through the admin interface) |
| `disableFullScreenWhenMasked` | `Boolean` | When `true`, full device screen sharing is automatically declined if any masked views are present. (Set through the admin interface) |
| `webviewMaskedElements` | `List<String>` | List of CSS selectors (e.g. `listOf(".credit-card")`) to redact inside WebViews enrolled with `redactWebView`. Merged with the dashboard **Masked elements** setting and with selectors passed to `redactWebView`; a local list never disables dashboard masking. |

##### Custom Authorization UI

`onSessionRequest` is only invoked when `requireAuthorizationForSession` resolves `true` — when authorization is disabled (locally or via the team's dashboard setting), sessions start without any prompt and the handler is never called.

You can replace the default authorization dialog with your own UI by providing an `onSessionRequest` listener:

```kotlin
val config = UpscopeConfiguration.Builder("YOUR_API_KEY")
    .requireAuthorizationForSession(true)
    .onSessionRequest(OnSessionRequestListener { response, agentName ->
        // Show your custom UI here
        // Call response.accept() or response.reject()
        myCustomDialog.show(agentName) { accepted ->
            if (accepted) response.accept() else response.reject()
        }
        // Return a Cancellable for cleanup if the request is dismissed externally
        Cancellable { myCustomDialog.dismiss() }
    })
    .build()
```

Similarly, use `onControlRequest` to customize the remote control authorization prompt. It is only invoked when `requireControlRequest` is enabled (which defaults to `false`):

```kotlin
val config = UpscopeConfiguration.Builder("YOUR_API_KEY")
    .requireControlRequest(true)
    .onControlRequest(OnControlRequestListener { response, agentName ->
        // Show your custom UI here
        myCustomDialog.show(agentName) { accepted ->
            if (accepted) response.accept() else response.reject()
        }
        // Return a Cancellable for cleanup if the request is withdrawn externally
        Cancellable { myCustomDialog.dismiss() }
    })
    .build()
```

Use `onFullDeviceRequest` to intercept an agent's request for full-device screen sharing before the system MediaProjection permission dialog appears. Show your own UI, then call `response.accept()` to proceed to the system prompt or `response.reject()` to decline and stay in in-app mode. When this listener is not set, the SDK shows the system permission dialog directly.

```kotlin
val config = UpscopeConfiguration.Builder("YOUR_API_KEY")
    .onFullDeviceRequest(OnFullDeviceRequestListener { response, agentName ->
        // Show your custom UI here
        myDialog.show(
            agentName = agentName,
            onAccept = { response.accept() },
            onDecline = { response.reject() }
        )
        // Return a Cancellable invoked if the request is withdrawn before the user responds
        Cancellable { myDialog.dismiss() }
    })
    .build()
```

The listener interface is:

```kotlin
fun interface OnFullDeviceRequestListener {
    fun onFullDeviceRequest(response: SessionRequestResponse, agentName: String?): Cancellable?
}
```

`SessionRequestResponse` exposes `accept()` and `reject()`. The returned `Cancellable` (or `null`) is called for cleanup if the request is withdrawn by the agent before the user responds.

##### Full Example

```kotlin
val config = UpscopeConfiguration.Builder("YOUR_API_KEY")
    .requireAuthorizationForSession(true)
    .authorizationPromptTitle("Screen Share")
    .authorizationPromptMessage("{%agentName%|Support} wants to help you")
    .showTerminateButton(true)
    .endOfSessionMessage("Session ended. Thank you!")
    .stopSessionText("End Session")
    .allowRemoteClick(true)
    .allowRemoteScroll(true)
    .enableLookupCodeOnShake(true)
    .lookupCodeKeyTitle("Your Code")
    .lookupCodeKeyMessage("Share this code: {%lookupCode%}")
    .translationsYes("Yes, share")
    .translationsNo("No thanks")
    .translationsOk("Got it")
    .region("us-east")
    .build()

Upscope.initialize(applicationContext, config)
```

#### SDK Functions

Source: https://cobrowsingapi.com/docs/sdk/android/sdk-functions

Here's a list of all the functions and properties supported by the Co-Browsing API Android SDK.

All methods and properties are accessed through the `Upscope` singleton object.

##### Connection Management

| Function | Description |
|----------|-------------|
| `connect()` | Establishes a WebSocket connection to the servers. |
| `disconnect()` | Closes the connection and ends any active session. |
| `reset(reconnect: Boolean = true)` | Resets the connection, clearing all stored identities and visitor data. Pass `false` to stay disconnected after reset. |

##### Session Control

| Function | Description |
|----------|-------------|
| `stopSession()` | Ends the current screen sharing session. |
| `requestAgent()` | Signals that the visitor wants assistance from an agent. |
| `cancelAgentRequest()` | Cancels a pending agent request. |
| `getLookupCode()` | Requests a 4-digit lookup code from the server. Access the code via the `lookupCode` property or `lookupCodeFlow`. |
| `sendCustomMessage(message: String)` | Sends a custom text or JSON message to the agent (max 5000 characters). |
| `stopRemoteControl()` | Revokes the agent's remote control of the device. The session continues; only the agent's ability to interact stops. Safe no-op if no agent has control. |
| `stopFullDeviceSharing()` | Stops full-device screen sharing and reverts to in-app screen sharing. Safe no-op if not active. |

##### State Properties

| Property | Type | Description |
|----------|------|-------------|
| `isConnected` | `Boolean` | Whether the SDK is currently connected to the server. |
| `isInSession` | `Boolean` | Whether a screen sharing session is currently active. |
| `connectionState` | `ConnectionState` | Current connection state (`Inactive`, `Connecting`, `Connected`, `Reconnecting`, `Error`). |
| `sessionState` | `SessionState` | Current session state (`INACTIVE`, `PENDING_REQUEST`, `ACTIVE`, `PAUSED`, `ENDED`). |
| `shortId` | `String?` | The visitor's unique short ID assigned by the server. |
| `lookupCode` | `String?` | The current 4-digit lookup code, if one has been generated. |
| `watchLink` | `String?` | The full URL where agents can view the session (`https://upscope.com/w/{shortId}`). |
| `remoteControlState` | `RemoteControlState` | Current remote control state (`INACTIVE`, `PENDING_REQUEST`, `ACTIVE`). |
| `fullDeviceSharingState` | `FullDeviceSharingState` | Current full-device sharing state (`INACTIVE`, `PENDING_REQUEST`, `ACTIVE`). |

##### Visitor Identification

You can set visitor identity either through direct property assignment or as a batch update.

###### Direct Properties

```kotlin
Upscope.uniqueId = "user-123"
Upscope.callName = "John Smith"
Upscope.tags = listOf("#VIP")
Upscope.identities = listOf("John Smith", "john@example.com")
Upscope.metadata = mapOf("plan" to "enterprise", "region" to "US")
```

###### Batch Update

Use `updateConnection()` to update multiple fields at once:

```kotlin
Upscope.updateConnection(
    uniqueId = "user-123",
    callName = "John Smith",
    tags = listOf("#VIP"),
    identities = listOf("John Smith", "john@example.com"),
    metadata = mapOf("plan" to "enterprise", "region" to "US")
)
```

Pass `null` to keep an existing value unchanged. Only non-null parameters are updated.

##### Reactive State (StateFlow)

Subscribe to state changes using Kotlin StateFlow:

```kotlin
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

// In a coroutine scope (e.g., viewModelScope, lifecycleScope)

// Connection state changes
launch {
    Upscope.connectionStateFlow.collect { state ->
        println("Connection: $state")
    }
}

// Session state changes
launch {
    Upscope.sessionStateFlow.collect { state ->
        println("Session: $state")
    }
}

// Short ID changes
launch {
    Upscope.shortIdFlow.collect { shortId ->
        println("Short ID: $shortId")
    }
}

// Lookup code changes
launch {
    Upscope.lookupCodeFlow.collect { code ->
        println("Lookup code: $code")
    }
}

// Session mode changes
launch {
    Upscope.sessionModeFlow.collect { mode ->
        println("Session mode: $mode")
    }
}

// Remote control state changes
launch {
    Upscope.remoteControlStateFlow.collect { state ->
        println("Remote control: $state")
    }
}

// Full device sharing state changes
launch {
    Upscope.fullDeviceSharingStateFlow.collect { state ->
        println("Full device sharing: $state")
    }
}
```

##### Masking

Hide sensitive content from agents during screen sharing.

| Property/Function | Description |
|-------------------|-------------|
| `maskSecureInputs` | `Boolean` — Automatically mask secure input fields. Default: `true`. |
| `addMaskedView(view: View)` | Register a view to be masked (hidden from agent). |
| `removeMaskedView(view: View)` | Unregister a masked view. |
| `allMaskedViews` | `List<View>` — All currently masked views. |

###### Example

```kotlin
// Mask a specific view
Upscope.addMaskedView(creditCardField)

// Later, remove the mask
Upscope.removeMaskedView(creditCardField)

// Disable automatic secure input masking
Upscope.maskSecureInputs = false
```

#### Listening for Events

Source: https://cobrowsingapi.com/docs/sdk/android/listening-for-events

You can listen for SDK events by implementing the `UpscopeListener` interface and assigning it to the `Upscope` object:

```kotlin
Upscope.listener = object : UpscopeListener {
    // Override methods you need
}
```

##### UpscopeListener Interface

All listener methods have default empty implementations, so you only need to override the ones you care about.

```kotlin
Upscope.listener = object : UpscopeListener {
    override fun onConnectionStateChanged(state: ConnectionState) {
        // Connection state changed
        when (state) {
            is ConnectionState.Inactive -> println("Inactive")
            is ConnectionState.Connecting -> println("Connecting...")
            is ConnectionState.Connected -> println("Connected")
            is ConnectionState.Reconnecting -> println("Reconnecting...")
            is ConnectionState.Error -> println("Error: ${state.error.message}")
        }
    }

    override fun onSessionStarted(agentName: String?) {
        println("Session started with ${agentName ?: "an agent"}")
    }

    override fun onSessionEnded(reason: SessionEndReason) {
        when (reason) {
            is SessionEndReason.UserStopped -> println("User ended session")
            is SessionEndReason.AgentStopped -> println("Agent ended session")
            is SessionEndReason.Timeout -> println("Session timed out")
            is SessionEndReason.Error -> println("Session error: ${reason.error.message}")
        }
    }

    override fun onCustomMessageReceived(message: String, viewerId: String) {
        println("Message from $viewerId: $message")
    }

    override fun onError(error: UpscopeError) {
        println("Error: ${error.code} - ${error.message}")
    }

    override fun onViewerJoined(viewer: Viewer) {
        println("Viewer joined: ${viewer.name ?: viewer.id}")
    }

    override fun onViewerLeft(viewerId: String) {
        println("Viewer left: $viewerId")
    }

    override fun onViewerCountChanged(count: Int) {
        println("Viewers: $count")
    }

    override fun onRemoteControlStateChanged(state: RemoteControlState) {
        when (state) {
            RemoteControlState.INACTIVE -> println("Remote control inactive")
            RemoteControlState.PENDING_REQUEST -> println("Agent requested control, awaiting the user's response")
            RemoteControlState.ACTIVE -> println("Remote control active")
        }
    }

    override fun onFullDeviceSharingStateChanged(state: FullDeviceSharingState) {
        when (state) {
            FullDeviceSharingState.INACTIVE -> println("Full device sharing inactive")
            FullDeviceSharingState.PENDING_REQUEST -> println("Agent requested full device sharing, awaiting the user's response")
            FullDeviceSharingState.ACTIVE -> println("Full device sharing active")
        }
    }
}
```

##### Event Reference

| Method | Description |
|--------|-------------|
| `onConnectionStateChanged(state)` | Called when the connection state changes. |
| `onSessionStarted(agentName)` | A screen sharing session has started. `agentName` is the agent's display name if available. |
| `onSessionEnded(reason)` | A session has ended. `reason` indicates why (user stopped, agent stopped, timeout, or error). |
| `onCustomMessageReceived(message, viewerId)` | A custom message was received from a viewer. |
| `onError(error)` | An SDK error occurred. |
| `onViewerJoined(viewer)` | An agent started viewing the session. The `Viewer` includes `id`, `name`, screen dimensions, and focus state. |
| `onViewerLeft(viewerId)` | An agent stopped viewing the session. |
| `onViewerCountChanged(count)` | The total number of active viewers changed. |
| `onRemoteControlStateChanged(state)` | Whether an agent currently has remote control of the device (ability to tap and scroll) changed. Independent of the session being active. `RemoteControlState`: `INACTIVE`, `PENDING_REQUEST`, `ACTIVE`. |
| `onFullDeviceSharingStateChanged(state)` | Whether full-device (entire screen) sharing is currently running changed, as opposed to default in-app screen sharing. `FullDeviceSharingState`: `INACTIVE`, `PENDING_REQUEST`, `ACTIVE`. |

#### Full Device Screen Sharing

Source: https://cobrowsingapi.com/docs/sdk/android/full-device-screen-sharing

By default, the Co-Browsing API Android SDK shares only your app's screen. With full device screen sharing, agents can see the entire device screen, including other apps, the home screen, and system UI. This uses Android's MediaProjection API.

##### How It Works

Full device screen sharing uses the **MediaProjection API** to capture the entire screen. When an agent requests full device mode, the SDK shows the system screen capture permission dialog. The user taps "Start now" to begin sharing, and a persistent notification indicates that screen capture is active.

A **foreground service** keeps the capture alive even when your app is in the background, so agents continue to see the screen.

##### Setup

Full device screen sharing requires both:

1. **Enabling it in the Co-Browsing API dashboard** (admin interface) — if disabled there, agents will not see the full device option during sessions
2. **Adding the required permissions** to your app's `AndroidManifest.xml`:

```xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
```

The SDK already declares the necessary service and activity components. It detects these permissions at runtime — if they are missing, full device screen sharing is disabled regardless of the dashboard setting, and any agent requests for it are automatically declined.

##### How agents trigger full device mode

Once connected, agents can switch to full device mode from the Co-Browsing API dashboard during an active session. When they do:

1. The system screen capture permission dialog appears
2. The user taps **Start now**
3. A notification appears indicating screen sharing is active
4. The agent sees the full device screen
5. The user can stop sharing at any time by dismissing the notification

If the user denies the permission or stops the capture, the SDK notifies the server automatically. The agent can also switch back to app-only mode at any time.

##### Custom Request UI

By default the system MediaProjection permission dialog appears immediately when an agent requests full-device mode. You can intercept this request to show your own UI first — for example, an explanation screen — by setting `onFullDeviceRequest` on the `UpscopeConfiguration.Builder`. Call `response.accept()` to proceed to the system dialog, or `response.reject()` to decline and remain in in-app screen sharing mode. See [Configuration Options](https://cobrowsingapi.com/docs/sdk/android/configuration-options.md) for the full usage example.

##### Limitations

- **Element masking** is not available in full device mode (the SDK cannot inspect views outside your app). If `disableFullScreenWhenMasked` is enabled and masked views are present, the SDK will automatically decline full device mode.
- **Remote control** (tap/scroll) only works within your app, not on the home screen or other apps
- **Drawing annotations** are not displayed in full device mode
- The user must explicitly grant the system screen capture permission — it cannot be started programmatically

### Flutter SDK

Source: https://cobrowsingapi.com/docs/sdk/flutter

**Beta:**
The mobile SDKs are currently in beta. If you encounter any issues, please [contact us](mailto:team@cobrowsingapi.com).

The Co-Browsing API Flutter SDK allows you to integrate screen sharing capabilities into your Flutter app. It works on both iOS and Android from a single codebase, with features for element redaction, visitor identification, and session management.

#### Requirements

- Flutter 3.19.0+
- Dart SDK 3.3.0+
- iOS 14.0+
- Android API 26+ (Android 8.0)

#### Features

- **Screen sharing**: Allow agents to view your app's screen in real-time
- **Element redaction**: Hide sensitive widgets during screen sharing with the `UpscopeMasked` widget
- **Visitor identification**: Identify users and link sessions to your CRM
- **Lookup codes**: Generate 4-digit codes for easy session joining
- **Reactive streams**: All state exposed as Dart `Stream`s for reactive UI binding

#### Installation

Source: https://cobrowsingapi.com/docs/sdk/flutter/installation

**Beta:**
The mobile SDKs are currently in beta. If you encounter any issues, please [contact us](mailto:team@cobrowsingapi.com).

##### Requirements

- Flutter 3.19.0+
- Dart SDK 3.3.0+
- iOS 14.0+
- Android API 26+ (Android 8.0)

##### Installation

```bash
flutter pub add upscopeio_flutter_sdk
```

Or add it manually to your `pubspec.yaml`:

```yaml
dependencies:
  upscopeio_flutter_sdk: ^2026.8.3
```

Then run:

```bash
flutter pub get
```

##### Initialization

Register the method channel and initialize the SDK early in your app:

```dart
import 'package:upscopeio_flutter_sdk/upscopeio_flutter_sdk.dart';

void main() {
  UpscopeMethodChannel.register();
  runApp(const MyApp());
}
```

Then initialize in your root widget:

```dart
import 'package:upscopeio_flutter_sdk/upscopeio_flutter_sdk.dart';

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    _initUpscope();
  }

  Future<void> _initUpscope() async {
    await Upscope.instance.initialize(
      UpscopeConfiguration(apiKey: 'YOUR_API_KEY'),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}
```

The SDK auto-connects by default. To disable this, set `autoConnect: false` in the configuration and call `Upscope.instance.connect()` manually when ready.

##### Public API Key

You can find your public API key in the installation page of your Co-Browsing API dashboard.

##### ConnectionState Name Collision

Flutter's `material.dart` exports its own `ConnectionState`. To avoid conflicts, hide it when importing the SDK:

```dart
import 'package:flutter/material.dart' hide ConnectionState;
import 'package:upscopeio_flutter_sdk/upscopeio_flutter_sdk.dart';
```

##### Full Device Screen Sharing

The Flutter SDK supports full device screen sharing, but it requires platform-specific setup:

- **iOS**: Requires a Broadcast Upload Extension. See the [iOS full device screen sharing guide](https://cobrowsingapi.com/docs/sdk/ios/full-device-screen-sharing.md).
- **Android**: Requires adding two permissions to your `AndroidManifest.xml`. See the [Android full device screen sharing guide](https://cobrowsingapi.com/docs/sdk/android/full-device-screen-sharing.md).

##### Lookup Code on Shake

By default, shaking the device will display the lookup code in a dialog. This can be disabled via configuration options.

#### Configuration Options

Source: https://cobrowsingapi.com/docs/sdk/flutter/configuration-options

You can customize the behavior of the **PRODUCT** Flutter SDK through configuration options.

##### Setting Configuration

Pass options when creating the `UpscopeConfiguration`:

```dart
final config = UpscopeConfiguration(
  apiKey: 'YOUR_API_KEY',
  requireAuthorizationForSession: true,
  authorizationPromptTitle: 'Screen Sharing Request',
  authorizationPromptMessage: 'Allow {%agentName%|Support} to view your screen?',
  endOfSessionMessage: 'Thanks for using screen sharing!',
  translationsYes: 'Allow',
  translationsNo: 'Decline',
);

await Upscope.instance.initialize(config);
```

##### Configuration Options

Each option resolves in this order: a value you pass here overrides the matching dashboard setting, which overrides the SDK's built-in default (shown in the **Default** column).

###### Session Authorization

| Option                           | Type      | Default                           | Description                                                         |
| -------------------------------- | --------- | --------------------------------- | ------------------------------------------------------------------- |
| `requireAuthorizationForSession` | `bool`    | `true`                            | Require user permission before screen sharing starts. The Flutter SDK always sends this value (default `true`), so the team's dashboard setting is not consulted for it. When set to `false`, sessions start silently: no prompt is shown and nothing is emitted on `onSessionRequest`, even with `customSessionRequestUI` enabled. |
| `authorizationPromptTitle`       | `String?` | (Set through the admin interface) | Custom title for the authorization dialog.                          |
| `authorizationPromptMessage`     | `String?` | (Set through the admin interface) | Custom message for the authorization dialog. Supports placeholders. |
| `customSessionRequestUI`         | `bool?`   | `false`                           | Replace the native authorization dialog with your own UI. When `true`, the SDK emits on the `onSessionRequest` stream instead of showing the native dialog; you must listen and call `respondToSessionRequest`, otherwise session requests stall. This only changes how the authorization prompt is presented, not whether it happens: if `requireAuthorizationForSession` is set to `false`, sessions start with no prompt and no event (its default is `true`). See [Listening for Events](https://cobrowsingapi.com/docs/sdk/flutter/listening-for-events.md). |

###### Message Placeholders

The `authorizationPromptMessage` supports these placeholders:

- `{%agentName%|fallback}` - Agent's name with a fallback if unavailable
- `{%currentDomain%}` - App name

Example:

```dart
authorizationPromptMessage: '{%agentName%|Our support team} would like to view your screen',
```

###### UI Display

| Option                | Type      | Default                           | Description                                                    |
| --------------------- | --------- | --------------------------------- | -------------------------------------------------------------- |
| `showTerminateButton` | `bool`    | `true`                            | Show a button in the banner to end the screen sharing session. |
| `showUpscopeLink`     | `bool?`   | `true`                            | Show the Co-Browsing API link to the user. Setting this to `false` only works if whitelabeling is included in your plan. |
| `endOfSessionMessage` | `String?` | (Set through the admin interface) | Message displayed when the session ends.                       |
| `stopSessionText`     | `String?` | (Set through the admin interface) | Custom text for the stop session button.                       |

###### Remote Control

| Option                  | Type      | Default                           | Description                                               |
| ----------------------- | --------- | --------------------------------- | --------------------------------------------------------- |
| `allowRemoteClick`      | `bool?`   | (Set through the admin interface) | Allow agents to remotely tap on the screen.               |
| `allowRemoteScroll`     | `bool?`   | (Set through the admin interface) | Allow agents to remotely scroll the screen.               |
| `requireControlRequest` | `bool?`   | `false` | Require user approval before agents can use remote input. Resolved from the value set here, else the team's dashboard setting, else `false`. When it resolves `false`, remote input is granted without a separate control request — no prompt and nothing on `onControlRequest`, even with `customControlRequestUI` enabled. |
| `controlRequestTitle`   | `String?` | (Set through the admin interface) | Custom title for the control request prompt.              |
| `controlRequestMessage` | `String?` | (Set through the admin interface) | Custom message for the control request prompt.            |
| `customControlRequestUI` | `bool?`  | `false`                           | Replace the native control request prompt with your own UI. When `true`, the SDK emits on the `onControlRequest` stream instead of showing the native prompt; you must listen and call `respondToControlRequest`, otherwise control requests stall. This only changes how the control prompt is presented, not whether it happens: the stream only emits when `requireControlRequest` resolves `true`. See [Listening for Events](https://cobrowsingapi.com/docs/sdk/flutter/listening-for-events.md). |

###### Lookup Code

| Option                    | Type      | Default                           | Description                                                            |
| ------------------------- | --------- | --------------------------------- | ---------------------------------------------------------------------- |
| `enableLookupCodeOnShake` | `bool?`   | (Set through the admin interface) | Show lookup code popup when device is shaken.                          |
| `lookupCodeKeyTitle`      | `String?` | (Set through the admin interface) | Custom title for the shake detection alert.                            |
| `lookupCodeKeyMessage`    | `String?` | (Set through the admin interface) | Custom message for shake alert. Supports `{%lookupCode%}` placeholder. |

###### Localization Strings

| Option            | Type      | Description                                             |
| ----------------- | --------- | ------------------------------------------------------- |
| `translationsYes` | `String?` | Custom text for "Allow" button in authorization prompt. |
| `translationsNo`  | `String?` | Custom text for "Deny" button in authorization prompt.  |
| `translationsOk`  | `String?` | Custom text for "OK" button.                            |

###### Multi-Language Translations

Every text option (titles, messages, and the strings above) also accepts a map keyed by language code instead of a single string. The translation matching the device language is shown, falling back to `en` if the device language isn't included:

```dart
translationsYes: {'en': 'Yes', 'it': 'Si'},
translationsNo: {'en': 'No', 'it': 'No'},
```

All of these can also be configured per language through the dashboard.

###### Full Device Sharing

These options configure full-device screen sharing behavior. For the iOS Broadcast Upload Extension setup (App Group and extension bundle id via Info.plist), see [Full Device Screen Sharing](https://cobrowsingapi.com/docs/sdk/flutter/full-device-screen-sharing.md).

| Option                       | Type      | Description                                                                                                                             |
| ---------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `allowFullScreen`            | `bool?`   | iOS and Android — allow agents to request full device screen sharing during sessions. Also requires the setup described in [Full Device Screen Sharing](https://cobrowsingapi.com/docs/sdk/flutter/full-device-screen-sharing.md). Default: set through the admin interface. |
| `disableFullScreenWhenMasked` | `bool?`   | When `true`, full-device sharing is declined while masked content is on screen. Set `false` to allow it even with masked content present. Default: set through the admin interface. |
| `customFullDeviceRequestUI`  | `bool?`   | iOS and Android — gate full-device requests behind your own UI. When `true`, the SDK emits on the `onFullDeviceRequest` stream instead of proceeding directly to the system permission prompt; you must listen and call `respondToFullDeviceRequest`, otherwise requests stall. Default `false` = straight to the system prompt. See [Listening for Events](https://cobrowsingapi.com/docs/sdk/flutter/listening-for-events.md). |

###### System Options

| Option        | Type      | Description                                                                                 |
| ------------- | --------- | ------------------------------------------------------------------------------------------- |
| `autoConnect` | `bool`    | Automatically connect on initialization. Default: `true` (set through the admin interface). |
| `region`      | `String?` | Server region for connections.                                                              |
| `onPremiseBaseEndpoint` | `String?` | The base endpoint of your [on-premise deployment](https://cobrowsingapi.com/docs/on-premise/quick-start.md) (your instance's `BASE_ENDPOINT`), e.g. `'https://cobrowsing.acmetech.com'`. When set, the SDK connects to your instance instead of the cloud servers, and `region` is ignored. |

##### Full Example

```dart
final config = UpscopeConfiguration(
  apiKey: 'YOUR_API_KEY',
  requireAuthorizationForSession: true,
  autoConnect: true,
  authorizationPromptTitle: 'Screen Share',
  authorizationPromptMessage: '{%agentName%|Support} wants to help you',
  showTerminateButton: true,
  endOfSessionMessage: 'Session ended. Thank you!',
  stopSessionText: 'End Session',
  allowRemoteClick: true,
  allowRemoteScroll: true,
  enableLookupCodeOnShake: true,
  lookupCodeKeyTitle: 'Your Code',
  lookupCodeKeyMessage: 'Share this code: {%lookupCode%}',
  translationsYes: 'Yes, share',
  translationsNo: 'No thanks',
  translationsOk: 'Got it',
  region: 'us-east',
);

await Upscope.instance.initialize(config);
```

#### SDK Functions

Source: https://cobrowsingapi.com/docs/sdk/flutter/sdk-functions

Here's a list of all the functions and properties supported by the Co-Browsing API Flutter SDK.

All methods are accessed through the `Upscope.instance` singleton.

##### Connection Management

| Function | Description |
|----------|-------------|
| `connect()` | Establishes a WebSocket connection to the servers. Returns `Future<void>`. |
| `disconnect()` | Closes the connection and ends any active session. Returns `Future<void>`. |
| `reset({bool reconnect = true})` | Resets the connection, clearing all stored identities and visitor data. Pass `reconnect: false` to stay disconnected after reset. Returns `Future<void>`. |

##### Session Control

| Function | Description |
|----------|-------------|
| `stopSession()` | Ends the current screen sharing session. Returns `Future<void>`. |
| `requestAgent()` | Signals that the visitor wants assistance from an agent. Returns `Future<void>`. |
| `cancelAgentRequest()` | Cancels a pending agent request. Returns `Future<void>`. |
| `getLookupCode()` | Requests a 4-digit lookup code from the server. Access the code via the `lookupCode` stream. Returns `Future<void>`. |
| `sendCustomMessage(String message)` | Sends a custom text or JSON message to the agent (max 5000 characters). Returns `Future<void>`. |
| `stopRemoteControl()` | Revokes the agent's remote control of the device. The session continues; only the agent's ability to interact stops. Safe no-op if no agent has control. Returns `Future<void>`. |
| `stopFullDeviceSharing()` | Stops full-device screen sharing and reverts to in-app screen sharing. Safe no-op if not active. Returns `Future<void>`. |
| `respondToFullDeviceRequest(String requestId, {required bool accept})` | Responds to an `onFullDeviceRequest` event. `accept: true` allows full-device sharing (proceeds to the system permission prompt); `accept: false` declines. Returns `Future<void>`. |
| `respondToControlRequest(String requestId, {required bool accept})` | Responds to an `onControlRequest` event. `accept: true` grants the agent remote control; `accept: false` declines. Returns `Future<void>`. |
| `respondToSessionRequest(String requestId, {required bool accept})` | Responds to an `onSessionRequest` event. `accept: true` starts the cobrowsing session; `accept: false` declines. Returns `Future<void>`. |

##### State

| Method | Return Type | Description |
|--------|-------------|-------------|
| `getShortId()` | `Future<String?>` | The visitor's unique short ID assigned by the server. |
| `getWatchLink()` | `Future<String?>` | The full URL where agents can view the session (`https://upscope.com/w/{shortId}`). |

##### Visitor Identification

Use `updateConnection()` to set or update visitor identity:

```dart
await Upscope.instance.updateConnection(
  uniqueId: 'user-123',
  callName: 'John Smith',
  tags: ['#VIP'],
  identities: ['John Smith', 'john@example.com'],
  metadata: {'plan': 'enterprise', 'region': 'US'},
);
```

Pass `null` to keep an existing value unchanged. Only non-null parameters are updated.

##### Reactive Streams

Subscribe to state changes using Dart `Stream`s. These work with `StreamBuilder` for reactive UI updates.

```dart
// Connection state changes
StreamBuilder<ConnectionState>(
  stream: Upscope.instance.connectionState,
  builder: (context, snapshot) {
    final state = snapshot.data;
    return Text('Connection: ${state?.name ?? "unknown"}');
  },
);

// Session state changes
StreamBuilder<SessionState>(
  stream: Upscope.instance.sessionState,
  builder: (context, snapshot) {
    final state = snapshot.data;
    return Text('Session: ${state?.name ?? "unknown"}');
  },
);

// Short ID changes
Upscope.instance.shortId.listen((shortId) {
  print('Short ID: $shortId');
});

// Lookup code changes
Upscope.instance.lookupCode.listen((code) {
  print('Lookup code: $code');
});
```

###### Available Streams

| Stream | Type | Description |
|--------|------|-------------|
| `connectionState` | `Stream<ConnectionState>` | Connection state changes (`inactive`, `connecting`, `connected`, `reconnecting`, `error`). |
| `sessionState` | `Stream<SessionState>` | Session state changes (`inactive`, `pendingRequest`, `active`, `paused`, `ended`). |
| `shortId` | `Stream<String?>` | The visitor's short ID. |
| `lookupCode` | `Stream<String?>` | The current lookup code. |
| `onSessionStarted` | `Stream<String?>` | Emits when a session begins. The value is the agent's name, if available. |
| `onSessionEnded` | `Stream<SessionEndReason>` | Emits the reason when a session ends. |
| `onViewerJoined` | `Stream<Viewer>` | Emits when an agent starts viewing. |
| `onViewerLeft` | `Stream<String>` | Emits the viewer ID when an agent stops viewing. |
| `onViewerCountChanged` | `Stream<int>` | Emits the current count of active viewers. |
| `onCustomMessageReceived` | `Stream<CustomMessage>` | Emits custom messages received from agents. |
| `onError` | `Stream<UpscopeError>` | Emits SDK-level errors. |
| `remoteControlState` | `Stream<RemoteControlState>` | Emits when remote control state changes (`inactive`, `pendingRequest`, `active`). See [Listening for Events](https://cobrowsingapi.com/docs/sdk/flutter/listening-for-events.md) for details. |
| `fullDeviceSharingState` | `Stream<FullDeviceSharingState>` | Emits when full-device sharing state changes (`inactive`, `pendingRequest`, `active`). See [Listening for Events](https://cobrowsingapi.com/docs/sdk/flutter/listening-for-events.md) for details. |
| `onFullDeviceRequest` | `Stream<FullDeviceRequest>` | Fires when an agent requests full-device sharing and the configuration sets `customFullDeviceRequestUI: true`. Emits a `FullDeviceRequest` with `requestId` and `agentName`; respond with `respondToFullDeviceRequest`. See [Listening for Events](https://cobrowsingapi.com/docs/sdk/flutter/listening-for-events.md) for details. |
| `onControlRequest` | `Stream<ControlRequest>` | Fires when an agent requests remote control and the configuration sets `customControlRequestUI: true`. Emits a `ControlRequest` with `requestId` and `agentName`; respond with `respondToControlRequest`. See [Listening for Events](https://cobrowsingapi.com/docs/sdk/flutter/listening-for-events.md) for details. |
| `onSessionRequest` | `Stream<SessionRequest>` | Fires when an agent requests to start a session, the configuration sets `customSessionRequestUI: true`, and `requireAuthorizationForSession` is enabled. Emits a `SessionRequest` with `requestId` and `agentName`; respond with `respondToSessionRequest`. See [Listening for Events](https://cobrowsingapi.com/docs/sdk/flutter/listening-for-events.md) for details. |

##### Masking

Hide sensitive content from agents during screen sharing using the `UpscopeMasked` widget.

```dart
UpscopeMasked(
  child: TextField(
    decoration: InputDecoration(labelText: 'Credit Card Number'),
    obscureText: true,
  ),
)
```

The `UpscopeMasked` widget automatically tracks its child's position and replaces the region with a black rectangle in the agent's view. The user sees the real content as normal.

#### Listening for Events

Source: https://cobrowsingapi.com/docs/sdk/flutter/listening-for-events

The Co-Browsing API Flutter SDK exposes all events as Dart `Stream`s. Subscribe to them for reactive event handling.

##### Listening for Events

```dart
import 'dart:async';
import 'package:upscopeio_flutter_sdk/upscopeio_flutter_sdk.dart';

class MyWidget extends StatefulWidget {
  const MyWidget({super.key});

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  final List<StreamSubscription> _subscriptions = [];

  @override
  void initState() {
    super.initState();

    _subscriptions.add(
      Upscope.instance.connectionState.listen((state) {
        switch (state) {
          case ConnectionState.inactive:
            print('Inactive');
          case ConnectionState.connecting:
            print('Connecting...');
          case ConnectionState.connected:
            print('Connected');
          case ConnectionState.reconnecting:
            print('Reconnecting...');
          case ConnectionState.error:
            print('Error');
        }
      }),
    );

    _subscriptions.add(
      Upscope.instance.onSessionStarted.listen((_) {
        print('Session started');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onSessionEnded.listen((reason) {
        switch (reason) {
          case SessionEndReason.userStopped:
            print('User ended session');
          case SessionEndReason.agentStopped:
            print('Agent ended session');
          case SessionEndReason.timeout:
            print('Session timed out');
          case SessionEndReason.error:
            print('Session error');
        }
      }),
    );

    _subscriptions.add(
      Upscope.instance.onCustomMessageReceived.listen((msg) {
        print('Message from ${msg.viewerId}: ${msg.message}');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onError.listen((error) {
        print('Error: ${error.code} - ${error.message}');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onViewerJoined.listen((viewer) {
        print('Viewer joined: ${viewer.name ?? viewer.id}');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onViewerLeft.listen((viewerId) {
        print('Viewer left: $viewerId');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onViewerCountChanged.listen((count) {
        print('Viewers: $count');
      }),
    );

    _subscriptions.add(
      Upscope.instance.remoteControlState.listen((state) {
        print('Remote control: ${state.name}');
      }),
    );

    _subscriptions.add(
      Upscope.instance.fullDeviceSharingState.listen((state) {
        print('Full device sharing: ${state.name}');
      }),
    );

    _subscriptions.add(
      Upscope.instance.onFullDeviceRequest.listen((request) {
        print('Full device request from ${request.agentName ?? "agent"}');
        // Only fires when the configuration sets customFullDeviceRequestUI:
        // true (otherwise the SDK proceeds directly to the system prompt).
        // accept: true proceeds to the system permission prompt; false declines
        Upscope.instance
            .respondToFullDeviceRequest(request.requestId, accept: true);
      }),
    );

    _subscriptions.add(
      Upscope.instance.onControlRequest.listen((request) {
        print('Control request from ${request.agentName ?? "agent"}');
        // Only fires when the configuration sets customControlRequestUI: true
        // (replacing the native control request prompt) and
        // requireControlRequest is enabled.
        // accept: true grants remote control; false declines
        Upscope.instance
            .respondToControlRequest(request.requestId, accept: true);
      }),
    );

    _subscriptions.add(
      Upscope.instance.onSessionRequest.listen((request) {
        print('Session request from ${request.agentName ?? "agent"}');
        // Only fires when the configuration sets customSessionRequestUI: true
        // (replacing the native authorization dialog) and authorization is
        // required.
        // accept: true starts the session; false declines
        Upscope.instance
            .respondToSessionRequest(request.requestId, accept: true);
      }),
    );
  }

  @override
  void dispose() {
    for (final sub in _subscriptions) {
      sub.cancel();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return const SizedBox.shrink();
  }
}
```

##### Event Reference

| Stream | Type | Description |
|--------|------|-------------|
| `connectionState` | `ConnectionState` | Connection state changed. |
| `onSessionStarted` | `String?` | A screen sharing session has started. Emits the agent's name, if available. |
| `onSessionEnded` | `SessionEndReason` | A session has ended. Reason indicates why (`userStopped`, `agentStopped`, `timeout`, or `error`). |
| `onCustomMessageReceived` | `CustomMessage` | A custom message was received from a viewer. Includes `message` and `viewerId`. |
| `onError` | `UpscopeError` | An SDK error occurred. Includes `code` and `message`. |
| `onViewerJoined` | `Viewer` | An agent started viewing the session. The `Viewer` includes `id`, `name`, and screen metrics. |
| `onViewerLeft` | `String` | An agent stopped viewing the session. Emits the viewer ID. |
| `onViewerCountChanged` | `int` | The total number of active viewers changed. |
| `remoteControlState` | `RemoteControlState` | Emits when whether an agent has remote control of the device changes (ability to tap and scroll). Enum values: `inactive`, `pendingRequest`, `active`. Independent of the session being active. |
| `fullDeviceSharingState` | `FullDeviceSharingState` | Emits when whether full-device (entire screen) sharing is running changes, as opposed to default in-app screen sharing. Enum values: `inactive`, `pendingRequest`, `active`. |
| `onFullDeviceRequest` | `FullDeviceRequest` | Fires when an agent requests full-device sharing, before the system permission prompt. Only fires when `customFullDeviceRequestUI: true` is set in the configuration — without the flag, the SDK proceeds directly to the system prompt. Emits a `FullDeviceRequest` with `requestId` and `agentName`. Respond with `Upscope.instance.respondToFullDeviceRequest(request.requestId, accept: ...)` — `true` allows (proceeds to the system prompt), `false` declines (stays in in-app mode). Without a listener that responds, full-device requests stall. |
| `onControlRequest` | `ControlRequest` | Fires when an agent requests remote control of the device. Only fires when `customControlRequestUI: true` is set in the configuration and `requireControlRequest` is enabled (note: `requireControlRequest` defaults to `false`, so control is otherwise granted without a request); the native control request prompt is not shown. Emits a `ControlRequest` with `requestId` and `agentName`. Respond with `Upscope.instance.respondToControlRequest(request.requestId, accept: ...)` — `true` grants control, `false` declines. Without a listener that responds, control requests stall. |
| `onSessionRequest` | `SessionRequest` | Fires when an agent requests to start a cobrowsing session. Only fires when `customSessionRequestUI: true` is set in the configuration and `requireAuthorizationForSession` is enabled; the native authorization dialog is not shown. Emits a `SessionRequest` with `requestId` and `agentName`. Respond with `Upscope.instance.respondToSessionRequest(request.requestId, accept: ...)` — `true` starts the session, `false` declines. Without a listener that responds, session requests stall and the session never starts. |

##### Using StreamBuilder

For reactive UI updates, use `StreamBuilder` instead of manual subscriptions:

```dart
StreamBuilder<ConnectionState>(
  stream: Upscope.instance.connectionState,
  builder: (context, snapshot) {
    if (!snapshot.hasData) return const SizedBox.shrink();
    return Text('Status: ${snapshot.data!.name}');
  },
)
```

#### Full Device Screen Sharing

Source: https://cobrowsingapi.com/docs/sdk/flutter/full-device-screen-sharing

By default, the Co-Browsing API Flutter SDK shares only your app's screen. With full device screen sharing, agents can see the entire device screen, including other apps, the home screen, and system UI.

On **iOS** this uses Apple's Broadcast Upload Extension (ReplayKit); on **Android** it uses the MediaProjection API. Both need a little native setup, described below.

**Physical devices only:**
Full device screen sharing only works on physical devices. It is not supported on the iOS Simulator or Android emulators.

Full device screen sharing must also be **enabled in the Co-Browsing API dashboard** (admin interface) in addition to the steps below. If it is disabled there, agents will not see the full device option during sessions.

##### iOS setup

Full device capture on iOS runs in a **Broadcast Upload Extension** — a separate native target in your app's Xcode project (under `ios/`). It sends frames to your app through an App Group, and the SDK relays them to the agent.

###### 1. Create the Broadcast Upload Extension target

Create the extension target **first** — the `Podfile` step below references it by name, so it must already exist in the Xcode project before you run `pod install`.

1. Open `ios/Runner.xcworkspace` in Xcode
2. Go to **File > New > Target**
3. Select **Broadcast Upload Extension**
4. **Uncheck "Include UI Extension"** — a UI extension adds a setup screen whose default handler never completes, so the broadcast would never start
5. Name it (e.g., `YourAppBroadcast`) — you'll reference this exact name in the `Podfile` next
6. Note the extension's **bundle identifier** (Xcode prefixes it with your app's, e.g. `com.yourcompany.yourapp.YourAppBroadcast`) — you'll need it in step 6

###### 2. Add the extension dependency

In your app's `ios/Podfile`, add the broadcast subspec to the **extension** target. Declare it as a **top-level target** (a sibling of `Runner`, not nested inside it). `use_frameworks!` is required so the extension's linkage matches the `Runner` target, which Flutter links as frameworks. Do not pin a version — it follows the `UpscopeSDK` version that `upscopeio_flutter_sdk` already requires:

```ruby
target 'YourAppBroadcast' do
  use_frameworks!
  pod 'UpscopeSDK/BroadcastExtension'
end
```

Then run `pod install` from the `ios/` directory.

###### 3. Configure App Groups

Both your main app (the `Runner` target) and the extension need to share data through an App Group:

1. Select the **Runner target** > Signing & Capabilities > **+ Capability** > **App Groups**
2. Add a group identifier (e.g., `group.com.yourcompany.yourapp`)
3. Select your **extension target** > Signing & Capabilities > **+ Capability** > **App Groups**
4. Add the **same** group identifier

###### 4. Configure the extension's Info.plist

Add the App Group identifier to the extension's `Info.plist`:

```xml
<key>UpscopeAppGroupId</key>
<string>group.com.yourcompany.yourapp</string>
```

###### 5. Implement the extension

Replace the contents of the extension's `SampleHandler.swift` with:

```swift
import UpscopeBroadcastExtension

class SampleHandler: UpscopeSampleHandler {}
```

All frame capture and forwarding is handled by `UpscopeSampleHandler`.

###### 6. Configure your iOS app's Info.plist

Add the App Group and extension bundle identifiers to your iOS **app's** `Info.plist` (`UpscopeAppGroupId` is the same key you added to the extension's `Info.plist` in step 4):

```xml
<key>UpscopeAppGroupId</key>
<string>group.com.yourcompany.yourapp</string>
<key>UpscopeBroadcastExtensionBundleId</key>
<string>com.yourcompany.yourapp.broadcast</string>
```

No code changes are needed — the native SDK picks these up automatically.

If the `UpscopeAppGroupId` key is missing from your app's `Info.plist`, the SDK will not advertise full device support on iOS, and agent requests for it are declined automatically.

By default, an agent's full device request goes straight to the system permission prompt. To show your own confirmation UI first, set `customFullDeviceRequestUI: true` in the configuration and **handle incoming requests** — see [Responding to full device requests](#responding-to-full-device-requests) below. With the flag set, requests stall silently unless a listener calls `respondToFullDeviceRequest`.

##### Android setup

Add the screen-capture permissions to `android/app/src/main/AndroidManifest.xml`:

```xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
```

The SDK already declares the capture service and activity. It detects these permissions at runtime — if they are missing, full device screen sharing is disabled regardless of the dashboard setting. No further configuration is required; Android shows the system screen-capture permission dialog when sharing starts.

##### Responding to full device requests

By default the SDK proceeds directly to the system permission prompt when an agent requests full device sharing. To gate the request behind your own UI, set `customFullDeviceRequestUI: true` in the configuration — the SDK then routes requests to your Dart code instead. You must listen on `onFullDeviceRequest` and respond, or requests will never proceed:

```dart
final requestSub = Upscope.instance.onFullDeviceRequest.listen((request) {
  // request.agentName is the requesting agent's name, if available.
  // Optionally show your own confirmation UI here, then respond:
  Upscope.instance
      .respondToFullDeviceRequest(request.requestId, accept: true); // accept: false to decline
});

final stateSub = Upscope.instance.fullDeviceSharingState.listen((state) {
  // FullDeviceSharingState.pendingRequest / .active / .inactive
  debugPrint('Full device sharing: $state');
});

// Cancel both subscriptions in dispose().
```

After you accept, iOS shows the system broadcast picker (the user taps **Start Broadcast**) and Android shows the screen-capture dialog (the user taps **Start now**). End sharing programmatically at any time:

```dart
Upscope.instance.stopFullDeviceSharing();
```

The user can also stop sharing from iOS **Control Center** or the Android capture notification; the SDK reports this via `fullDeviceSharingState`.

##### Responding to control requests

Remote control requests follow the same pattern, but are opt-in: set `customControlRequestUI: true` in the configuration to replace the native control request prompt. The SDK then routes requests to your Dart code — listen on `onControlRequest` and respond with `respondToControlRequest`:

```dart
final controlRequestSub = Upscope.instance.onControlRequest.listen((request) {
  // request.agentName is the requesting agent's name, if available.
  // Optionally show your own confirmation UI here, then respond:
  Upscope.instance
      .respondToControlRequest(request.requestId, accept: true); // accept: false to decline
});

final controlStateSub = Upscope.instance.remoteControlState.listen((state) {
  // RemoteControlState.pendingRequest / .active / .inactive
  debugPrint('Remote control: $state');
});

// Cancel both subscriptions in dispose().
```

Revoke control at any time with `Upscope.instance.stopRemoteControl()`.

##### Limitations

- Works on **physical devices** only (not the iOS Simulator or Android emulators)
- **Element masking** is not available in full device mode (the SDK cannot inspect views outside your app)
- **Remote control** only works within your app, not on the home screen or other apps
- **Drawing annotations** are not displayed in full device mode
- The user must explicitly start the broadcast/capture via the system prompt — it cannot be started programmatically

### React Native SDK

Source: https://cobrowsingapi.com/docs/sdk/react-native

**Beta:**
The mobile SDKs are currently in beta. If you encounter any issues, please [contact us](mailto:team@cobrowsingapi.com).

The Co-Browsing API React Native SDK allows you to integrate screen sharing capabilities into your React Native app. It works on both iOS and Android from a single codebase, with features for element redaction, visitor identification, and session management.

#### Requirements

- React Native 0.76.0+ (New Architecture required)
- React 18.2.0+
- iOS 14.0+
- Android API 26+ (Android 8.0)

#### Features

- **Screen sharing**: Allow agents to view your app's screen in real-time
- **Element redaction**: Hide sensitive content during screen sharing with the `UpscopeMasked` component
- **Visitor identification**: Identify users and link sessions to your CRM
- **Lookup codes**: Generate 4-digit codes for easy session joining
- **React hooks**: Reactive hooks for connection state, session state, and more

#### Installation

Source: https://cobrowsingapi.com/docs/sdk/react-native/installation

**Beta:**
The mobile SDKs are currently in beta. If you encounter any issues, please [contact us](mailto:team@cobrowsingapi.com).

##### Requirements

- React Native 0.76.0+ (New Architecture required)
- React 18.2.0+
- iOS 14.0+
- Android API 26+ (Android 8.0)

##### Installation

```bash
npm install @upscopeio/react-native-sdk
```

###### iOS Setup

The React Native SDK depends on the native `UpscopeSDK` iOS framework, which must be added via Swift Package Manager:

1. Open your iOS project in Xcode (`ios/YourApp.xcworkspace`)
2. Go to **File** > **Add Package Dependencies**
3. Enter `https://github.com/upscopeio/cobrowsing-ios.git`
4. Select the version and add to your app target

Then install the CocoaPods bridge:

```bash
cd ios && pod install
```

###### Android Setup

No additional steps required. The native Android SDK is linked automatically via Gradle.

##### Initialization

Initialize the SDK early in your app, before any components that use Upscope hooks mount:

```typescript
import Upscope from '@upscopeio/react-native-sdk';

Upscope.initialize({
  apiKey: 'YOUR_API_KEY',
  autoConnect: true,
});
```

###### Full Example

```typescript
import React, { useEffect } from 'react';
import { SafeAreaView } from 'react-native';
import Upscope from '@upscopeio/react-native-sdk';

export default function App() {
  useEffect(() => {
    Upscope.initialize({
      apiKey: 'YOUR_API_KEY',
      autoConnect: true,
    });

    Upscope.updateConnection({
      uniqueId: 'user-123',
      callName: 'John Smith',
    });
  }, []);

  return (
    <SafeAreaView>
      {/* Your app content */}
    </SafeAreaView>
  );
}
```

The SDK auto-connects by default. To disable this, set `autoConnect: false` and call `Upscope.connect()` manually when ready.

##### Public API Key

You can find your public API key in the installation page of your Co-Browsing API dashboard.

##### Full Device Screen Sharing

The React Native SDK supports full device screen sharing, but it requires platform-specific setup:

- **iOS**: Requires a Broadcast Upload Extension. See the [iOS full device screen sharing guide](https://cobrowsingapi.com/docs/sdk/ios/full-device-screen-sharing.md).
- **Android**: Requires adding two permissions to your `AndroidManifest.xml`. See the [Android full device screen sharing guide](https://cobrowsingapi.com/docs/sdk/android/full-device-screen-sharing.md).

##### Lookup Code on Shake

By default, shaking the device will display the lookup code in a dialog. This can be disabled via configuration options.

#### Configuration Options

Source: https://cobrowsingapi.com/docs/sdk/react-native/configuration-options

You can customize the behavior of the **PRODUCT** React Native SDK through configuration options.

##### Setting Configuration

Pass options when calling `initialize()`:

```typescript
import Upscope from "@upscopeio/react-native-sdk";

Upscope.initialize({
  apiKey: "YOUR_API_KEY",
  requireAuthorizationForSession: true,
  authorizationPromptTitle: "Screen Sharing Request",
  authorizationPromptMessage:
    "Allow {%agentName%|Support} to view your screen?",
  endOfSessionMessage: "Thanks for using screen sharing!",
  translationsYes: "Allow",
  translationsNo: "Decline",
});
```

##### Configuration Options

Each option resolves in this order: a value you pass here overrides the matching dashboard setting, which overrides the SDK's built-in default (shown in the **Default** column).

###### Session Authorization

| Option                           | Type      | Default                           | Description                                                         |
| -------------------------------- | --------- | --------------------------------- | ------------------------------------------------------------------- |
| `requireAuthorizationForSession` | `boolean` | `true`                            | Require user permission before screen sharing starts. Resolved from the value set here, else the team's dashboard setting, else `true`. When it resolves `false`, sessions start silently: no prompt is shown and no `sessionRequest` event fires, even with `customSessionRequestUI` enabled. |
| `authorizationPromptTitle`       | `string`  | (Set through the admin interface) | Custom title for the authorization dialog.                          |
| `authorizationPromptMessage`     | `string`  | (Set through the admin interface) | Custom message for the authorization dialog. Supports placeholders. |
| `customSessionRequestUI`         | `boolean` | `false`                           | Replace the native authorization dialog with your own UI. When `true`, the SDK emits a `sessionRequest` event instead of showing the native dialog; you must subscribe and call `respondToSessionRequest`, otherwise session requests stall. This only changes how the authorization prompt is presented, not whether it happens: if `requireAuthorizationForSession` resolves `false`, sessions start with no prompt and no event. Apps that need the event to always fire should also set `requireAuthorizationForSession: true`. See [Listening for Events](https://cobrowsingapi.com/docs/sdk/react-native/listening-for-events.md). |

###### Message Placeholders

The `authorizationPromptMessage` supports these placeholders:

- `{%agentName%|fallback}` - Agent's name with a fallback if unavailable
- `{%currentDomain%}` - App name

Example:

```typescript
authorizationPromptMessage: '{%agentName%|Our support team} would like to view your screen',
```

###### UI Display

| Option                | Type      | Default                           | Description                                                    |
| --------------------- | --------- | --------------------------------- | -------------------------------------------------------------- |
| `showTerminateButton` | `boolean` | (Set through the admin interface) | Show a button in the banner to end the screen sharing session. |
| `showUpscopeLink`     | `boolean` | `true`                            | Show the Co-Browsing API link to the user. Setting this to `false` only works if whitelabeling is included in your plan. |
| `endOfSessionMessage` | `string`  | (Set through the admin interface) | Message displayed when the session ends.                       |
| `stopSessionText`     | `string`  | (Set through the admin interface) | Custom text for the stop session button.                       |

###### Remote Control

| Option                  | Type      | Default                           | Description                                               |
| ----------------------- | --------- | --------------------------------- | --------------------------------------------------------- |
| `allowRemoteClick`      | `boolean` | `true`                            | Allow agents to remotely tap on the screen.               |
| `allowRemoteScroll`     | `boolean` | `true`                            | Allow agents to remotely scroll the screen.               |
| `requireControlRequest` | `boolean` | `false` | Require user approval before agents can use remote input. Resolved from the value set here, else the team's dashboard setting, else `false`. When it resolves `false`, remote input is granted without a separate control request — no prompt and no `controlRequest` event, even with `customControlRequestUI` enabled. |
| `controlRequestTitle`   | `string`  | (Set through the admin interface) | Custom title for the control request prompt.              |
| `controlRequestMessage` | `string`  | (Set through the admin interface) | Custom message for the control request prompt.            |
| `customControlRequestUI` | `boolean` | `false`                          | Replace the native control request prompt with your own UI. When `true`, the SDK emits a `controlRequest` event instead of showing the native prompt; you must subscribe and call `respondToControlRequest`, otherwise control requests stall. This only changes how the control prompt is presented, not whether it happens: the event fires only when `requireControlRequest` resolves `true`. See [Listening for Events](https://cobrowsingapi.com/docs/sdk/react-native/listening-for-events.md). |

###### Lookup Code

| Option                    | Type      | Default                           | Description                                                            |
| ------------------------- | --------- | --------------------------------- | ---------------------------------------------------------------------- |
| `enableLookupCodeOnShake` | `boolean` | (Set through the admin interface) | Show lookup code popup when device is shaken.                          |
| `lookupCodeKeyTitle`      | `string`  | (Set through the admin interface) | Custom title for the shake detection alert.                            |
| `lookupCodeKeyMessage`    | `string`  | (Set through the admin interface) | Custom message for shake alert. Supports `{%lookupCode%}` placeholder. |

###### Localization Strings

| Option            | Type     | Description                                             |
| ----------------- | -------- | ------------------------------------------------------- |
| `translationsYes` | `string` | Custom text for "Allow" button in authorization prompt. |
| `translationsNo`  | `string` | Custom text for "Deny" button in authorization prompt.  |
| `translationsOk`  | `string` | Custom text for "OK" button.                            |

###### Multi-Language Translations

Every text option (titles, messages, and the strings above) also accepts an object keyed by language code instead of a single string. The translation matching the device language is shown, falling back to `en` if the device language isn't included:

```typescript
translationsYes: { en: "Yes", it: "Si" },
translationsNo: { en: "No", it: "No" },
```

All of these can also be configured per language through the dashboard.

###### Full-Device Screen Sharing (iOS)

| Option                          | Type     | Description                                                                                              |
| ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `allowFullScreen`               | `boolean` | iOS and Android — allow agents to request full device screen sharing during sessions. Also requires the setup described in [Full Device Screen Sharing](https://cobrowsingapi.com/docs/sdk/react-native/full-device-screen-sharing.md). Default: set through the admin interface. |
| `disableFullScreenWhenMasked`   | `boolean` | When `true`, full-device sharing is declined while masked content is on screen. Set `false` to allow it even with masked content present. Default: set through the admin interface. |
| `customFullDeviceRequestUI`     | `boolean` | iOS and Android — gate full-device requests behind your own UI. When `true`, the SDK emits a `fullDeviceRequest` event instead of proceeding directly to the system permission prompt; you must subscribe and call `respondToFullDeviceRequest`, otherwise requests stall. Default `false` = straight to the system prompt. See [Listening for Events](https://cobrowsingapi.com/docs/sdk/react-native/listening-for-events.md). |

###### System Options

| Option        | Type      | Description                                                                                 |
| ------------- | --------- | ------------------------------------------------------------------------------------------- |
| `autoConnect` | `boolean` | Automatically connect on initialization. Default: `true` (set through the admin interface). |
| `region`      | `string`  | Server region for connections.                                                              |
| `onPremiseBaseEndpoint` | `string` | The base endpoint of your [on-premise deployment](https://cobrowsingapi.com/docs/on-premise/quick-start.md) (your instance's `BASE_ENDPOINT`), e.g. `"https://cobrowsing.acmetech.com"`. When set, the SDK connects to your instance instead of the cloud servers, and `region` is ignored. |

##### Full Example

```typescript
Upscope.initialize({
  apiKey: "YOUR_API_KEY",
  autoConnect: true,
  requireAuthorizationForSession: true,
  authorizationPromptTitle: "Screen Share",
  authorizationPromptMessage: "{%agentName%|Support} wants to help you",
  showTerminateButton: true,
  endOfSessionMessage: "Session ended. Thank you!",
  stopSessionText: "End Session",
  allowRemoteClick: true,
  allowRemoteScroll: true,
  enableLookupCodeOnShake: true,
  lookupCodeKeyTitle: "Your Code",
  lookupCodeKeyMessage: "Share this code: {%lookupCode%}",
  translationsYes: "Yes, share",
  translationsNo: "No thanks",
  translationsOk: "Got it",
  region: "us-east",
});
```

#### SDK Functions

Source: https://cobrowsingapi.com/docs/sdk/react-native/sdk-functions

Here's a list of all the functions and hooks supported by the Co-Browsing API React Native SDK.

##### Imperative API

All methods are accessed through the default `Upscope` import or the `useUpscope()` hook.

```typescript
import Upscope from '@upscopeio/react-native-sdk';
```

###### Connection Management

| Function | Description |
|----------|-------------|
| `connect()` | Establishes a WebSocket connection to the servers. |
| `disconnect()` | Closes the connection and ends any active session. |
| `reset(reconnect?: boolean)` | Resets the connection, clearing all stored identities and visitor data. Pass `false` to stay disconnected after reset. Defaults to `true`. |

###### Session Control

| Function | Description |
|----------|-------------|
| `stopSession()` | Ends the current screen sharing session. |
| `requestAgent()` | Signals that the visitor wants assistance from an agent. |
| `cancelAgentRequest()` | Cancels a pending agent request. |
| `getLookupCode()` | Requests a 4-digit lookup code from the server. Access the code via the `useLookupCode()` hook or `lookupCodeChanged` event. |
| `sendCustomMessage(message: string)` | Sends a custom text or JSON message to the agent (max 5000 characters). |
| `stopRemoteControl()` | Revokes the agent's remote control of the device. The session continues; only the agent's ability to interact stops. Safe no-op if no agent has control. |
| `stopFullDeviceSharing()` | Stops full-device screen sharing and reverts to in-app screen sharing. Safe no-op if not active. |
| `respondToSessionRequest(requestId: string, accept: boolean)` | Responds to a `sessionRequest` event. Pass `true` to start the cobrowsing session or `false` to decline. |
| `respondToControlRequest(requestId: string, accept: boolean)` | Responds to a `controlRequest` event. Pass `true` to grant the agent remote control of the device or `false` to decline. |
| `respondToFullDeviceRequest(requestId: string, accept: boolean)` | Responds to a `fullDeviceRequest` event. Pass `true` to allow full-device sharing (proceeds to the system permission prompt) or `false` to decline. |

###### State

| Function | Return Type | Description |
|----------|-------------|-------------|
| `getShortId()` | `Promise<string \| null>` | The visitor's unique short ID assigned by the server. |
| `getWatchLink()` | `Promise<string \| null>` | The full URL where agents can view the session (`https://upscope.com/w/{shortId}`). |

###### Visitor Identification

Use `updateConnection()` to set or update visitor identity:

```typescript
Upscope.updateConnection({
  uniqueId: 'user-123',
  callName: 'John Smith',
  tags: ['#VIP'],
  identities: ['John Smith', 'john@example.com'],
  metadata: { plan: 'enterprise', region: 'US' },
});
```

Only provided fields are updated. Omit a field to keep its existing value.

##### React Hooks

All hooks are reactive — components re-render automatically when the underlying state changes.

```typescript
import {
  useConnectionState,
  useSessionState,
  useShortId,
  useLookupCode,
  useRemoteControlState,
  useFullDeviceSharingState,
  useUpscope,
} from '@upscopeio/react-native-sdk';
```

###### `useConnectionState()`

Returns the current connection state.

```typescript
const connectionState = useConnectionState();
// "inactive" | "connecting" | "connected" | "reconnecting" | "error"
```

###### `useSessionState()`

Returns the current session state.

```typescript
const sessionState = useSessionState();
// "inactive" | "pendingRequest" | "active" | "paused" | "ended"
```

###### `useShortId()`

Returns the visitor's short ID, or `null` if not yet assigned.

```typescript
const shortId = useShortId();
```

###### `useLookupCode()`

Returns the current lookup code, or `null` if not yet generated.

```typescript
const lookupCode = useLookupCode();
```

###### `useRemoteControlState()`

Returns whether an agent currently has remote control of the device.

```typescript
const remoteControlState = useRemoteControlState();
// "inactive" | "pendingRequest" | "active"
```

###### `useFullDeviceSharingState()`

Returns whether full-device screen sharing is currently running.

```typescript
const fullDeviceSharingState = useFullDeviceSharingState();
// "inactive" | "pendingRequest" | "active"
```

###### `useUpscope()`

Returns a stable object of imperative action methods. Safe to use as a dependency in `useCallback` / `useEffect`.

```typescript
const {
  connect,
  disconnect,
  stopSession,
  requestAgent,
  cancelAgentRequest,
  sendCustomMessage,
  getLookupCode,
  reset,
  stopRemoteControl,
  stopFullDeviceSharing,
  respondToSessionRequest,
  respondToControlRequest,
  respondToFullDeviceRequest,
} = useUpscope();
```

##### Masking

Hide sensitive content from agents during screen sharing using the `UpscopeMasked` component.

```tsx
import { UpscopeMasked } from '@upscopeio/react-native-sdk';

<UpscopeMasked>
  <TextInput secureTextEntry placeholder="Credit Card Number" />
</UpscopeMasked>
```

`UpscopeMasked` accepts all standard `View` props and replaces the wrapped region with a black rectangle in the agent's view. The user sees the real content as normal.

#### Listening for Events

Source: https://cobrowsingapi.com/docs/sdk/react-native/listening-for-events

You can listen for SDK events using `Upscope.addListener()`. Always remove subscriptions on cleanup to avoid memory leaks.

```typescript
import Upscope from '@upscopeio/react-native-sdk';
```

##### Listening for Events

```typescript
import React, { useEffect } from 'react';
import Upscope from '@upscopeio/react-native-sdk';

function MyComponent() {
  useEffect(() => {
    const subs = [
      Upscope.addListener('connectionStateChanged', ({ state, error }) => {
        switch (state) {
          case 'inactive':
            console.log('Inactive');
            break;
          case 'connecting':
            console.log('Connecting...');
            break;
          case 'connected':
            console.log('Connected');
            break;
          case 'reconnecting':
            console.log('Reconnecting...');
            break;
          case 'error':
            console.log('Error:', error?.message);
            break;
        }
      }),

      Upscope.addListener('sessionStarted', ({ agentName }) => {
        console.log(`Session started with ${agentName ?? 'an agent'}`);
      }),

      Upscope.addListener('sessionEnded', ({ reason, error }) => {
        switch (reason) {
          case 'userStopped':
            console.log('User ended session');
            break;
          case 'agentStopped':
            console.log('Agent ended session');
            break;
          case 'timeout':
            console.log('Session timed out');
            break;
          case 'error':
            console.log('Session error:', error?.message);
            break;
        }
      }),

      Upscope.addListener('customMessageReceived', ({ message, viewerId }) => {
        console.log(`Message from ${viewerId}: ${message}`);
      }),

      Upscope.addListener('error', ({ code, message }) => {
        console.log(`Error: ${code} - ${message}`);
      }),

      Upscope.addListener('viewerJoined', (viewer) => {
        console.log(`Viewer joined: ${viewer.name ?? viewer.id}`);
      }),

      Upscope.addListener('viewerLeft', ({ viewerId }) => {
        console.log(`Viewer left: ${viewerId}`);
      }),

      Upscope.addListener('viewerCountChanged', ({ count }) => {
        console.log(`Viewers: ${count}`);
      }),

      Upscope.addListener('remoteControlStateChanged', ({ state }) => {
        console.log(`Remote control: ${state}`);
      }),

      Upscope.addListener('fullDeviceSharingStateChanged', ({ state }) => {
        console.log(`Full-device sharing: ${state}`);
      }),

      Upscope.addListener('sessionRequest', ({ requestId, agentName }) => {
        // An agent is requesting to start a cobrowsing session. Only fires
        // when the config sets customSessionRequestUI: true (replacing the
        // native authorization dialog) and authorization is required.
        // Pass true to start the session, false to decline.
        const userAllowsSession = true;
        console.log(`${agentName ?? 'An agent'} requested a session`);
        Upscope.respondToSessionRequest(requestId, userAllowsSession);
      }),

      Upscope.addListener('controlRequest', ({ requestId, agentName }) => {
        // An agent is requesting remote control of the device. Only fires
        // when the config sets customControlRequestUI: true (replacing the
        // native control request prompt) and requireControlRequest is
        // enabled.
        // Pass true to grant control, false to decline.
        const userGrantsControl = true;
        console.log(`${agentName ?? 'An agent'} requested remote control`);
        Upscope.respondToControlRequest(requestId, userGrantsControl);
      }),

      Upscope.addListener('fullDeviceRequest', ({ requestId, agentName }) => {
        // Only fires when the config sets customFullDeviceRequestUI: true
        // (otherwise the SDK proceeds directly to the system prompt).
        // Pass true to proceed to the prompt, false to stay in in-app mode.
        const userWantsFullDevice = true;
        console.log(`${agentName ?? 'An agent'} requested full-device sharing`);
        Upscope.respondToFullDeviceRequest(requestId, userWantsFullDevice);
      }),
    ];

    return () => subs.forEach((sub) => sub.remove());
  }, []);

  return null;
}
```

##### Event Reference

| Event | Payload | Description |
|-------|---------|-------------|
| `connectionStateChanged` | `{ state, error? }` | Called when the connection state changes. `error` is present when `state` is `"error"`. |
| `sessionStarted` | `{ agentName }` | A screen sharing session has started. `agentName` is the agent's display name if available. |
| `sessionEnded` | `{ reason, error? }` | A session has ended. `reason` indicates why (`userStopped`, `agentStopped`, `timeout`, or `error`). |
| `customMessageReceived` | `{ message, viewerId }` | A custom message was received from a viewer. |
| `error` | `{ code, message }` | An SDK error occurred. |
| `viewerJoined` | `Viewer` | A viewer/agent joined the session. The `Viewer` includes `id`, `name`, screen dimensions, and focus state. |
| `viewerLeft` | `{ viewerId }` | A viewer/agent left the session. |
| `viewerCountChanged` | `{ count }` | The total number of active viewers changed. |
| `remoteControlStateChanged` | `{ state }` | Whether an agent currently has remote control of the device (ability to tap and scroll) changed. `state` is `'inactive' \| 'pendingRequest' \| 'active'`. Independent of the session being active. |
| `fullDeviceSharingStateChanged` | `{ state }` | Whether full-device (entire screen) sharing is currently running changed, as opposed to default in-app screen sharing. `state` is `'inactive' \| 'pendingRequest' \| 'active'`. |
| `sessionRequest` | `{ requestId, agentName }` | An agent requested to start a cobrowsing session. Only fires when `customSessionRequestUI: true` is set in the config and `requireAuthorizationForSession` is enabled; the native authorization dialog is not shown. `agentName` is the agent's display name if available, otherwise `null`. Respond with `Upscope.respondToSessionRequest(requestId, accept)` to start or decline the session — without a listener that responds, session requests stall and the session never starts. |
| `controlRequest` | `{ requestId, agentName }` | An agent requested remote control of the device. Only fires when `customControlRequestUI: true` is set in the config and `requireControlRequest` is enabled (note: `requireControlRequest` defaults to `false`, so control is otherwise granted without a request); the native control request prompt is not shown. `agentName` is the agent's display name if available, otherwise `null`. Respond with `Upscope.respondToControlRequest(requestId, accept)` to grant or decline control — without a listener that responds, control requests stall. |
| `fullDeviceRequest` | `{ requestId, agentName }` | An agent requested full-device screen sharing, before the system permission prompt appears. Only fires when `customFullDeviceRequestUI: true` is set in the config — without the flag, the SDK proceeds directly to the system prompt. `agentName` is the agent's display name if available, otherwise `null`. Respond with `Upscope.respondToFullDeviceRequest(requestId, accept)` to allow (continues to the system prompt) or decline (stays in in-app mode) — without a listener that responds, full-device requests stall. |
| `shortIdChanged` | `{ shortId }` | The visitor's short ID was assigned or changed. |
| `lookupCodeChanged` | `{ lookupCode }` | The lookup code was issued or changed. |

##### Using Hooks Instead

For most use cases, the reactive hooks are simpler than manual event subscriptions. See [SDK Functions](https://cobrowsingapi.com/docs/sdk/react-native/sdk-functions.md) for details on `useConnectionState()`, `useSessionState()`, and other hooks.

#### Full Device Screen Sharing

Source: https://cobrowsingapi.com/docs/sdk/react-native/full-device-screen-sharing

By default, the Co-Browsing API React Native SDK shares only your app's screen. With full device screen sharing, agents can see the entire device screen, including other apps, the home screen, and system UI.

On **iOS** this uses Apple's Broadcast Upload Extension (ReplayKit); on **Android** it uses the MediaProjection API. Both need a little native setup, described below.

**Physical devices only:**
Full device screen sharing only works on physical devices. It is not supported on the iOS Simulator or Android emulators.

Full device screen sharing must also be **enabled in the Co-Browsing API dashboard** (admin interface) in addition to the steps below. If it is disabled there, agents will not see the full device option during sessions.

##### iOS setup

Full device capture on iOS runs in a **Broadcast Upload Extension** — a separate native target in your app's Xcode project (under `ios/`). It sends frames to your app through an App Group, and the SDK relays them to the agent.

###### 1. Create the Broadcast Upload Extension target

Create the extension target **first** — the `Podfile` step below references it by name, so it must already exist in the Xcode project before you run `pod install`.

1. Open `ios/YourApp.xcworkspace` in Xcode
2. Go to **File > New > Target**
3. Select **Broadcast Upload Extension**
4. **Uncheck "Include UI Extension"** — a UI extension adds a setup screen whose default handler never completes, so the broadcast would never start
5. Name it (e.g., `YourAppBroadcast`) — you'll reference this exact name in the `Podfile` next
6. Note the extension's **bundle identifier** (Xcode prefixes it with your app's, e.g. `com.yourcompany.yourapp.YourAppBroadcast`) — you'll need it in step 6

###### 2. Add the extension dependency

In your app's `ios/Podfile`, add the broadcast subspec to the **extension** target, declared as a **top-level target** (not nested inside your app target). Do not pin a version — it follows the `UpscopeSDK` version that `@upscopeio/react-native-sdk` already requires:

```ruby
target 'YourAppBroadcast' do
  pod 'UpscopeSDK/BroadcastExtension'
end
```

If your app's targets are configured with `use_frameworks!`, add it to this target too so the extension's linkage matches its host. Then run `pod install` from the `ios/` directory.

###### 3. Configure App Groups

Both your main app and the extension need to share data through an App Group:

1. Select your **main app target** > Signing & Capabilities > **+ Capability** > **App Groups**
2. Add a group identifier (e.g., `group.com.yourcompany.yourapp`)
3. Select your **extension target** > Signing & Capabilities > **+ Capability** > **App Groups**
4. Add the **same** group identifier

###### 4. Configure the extension's Info.plist

Add the App Group identifier to the extension's `Info.plist`:

```xml
<key>UpscopeAppGroupId</key>
<string>group.com.yourcompany.yourapp</string>
```

###### 5. Implement the extension

Replace the contents of the extension's `SampleHandler.swift` with:

```swift
import UpscopeBroadcastExtension

class SampleHandler: UpscopeSampleHandler {}
```

All frame capture and forwarding is handled by `UpscopeSampleHandler`.

###### 6. Configure your iOS app's Info.plist

Add the App Group and extension bundle identifiers to your iOS **app's** `Info.plist` (`UpscopeAppGroupId` is the same key you added to the extension's `Info.plist` in step 4):

```xml
<key>UpscopeAppGroupId</key>
<string>group.com.yourcompany.yourapp</string>
<key>UpscopeBroadcastExtensionBundleId</key>
<string>com.yourcompany.yourapp.broadcast</string>
```

No code changes are needed — the native SDK picks these up automatically.

If the `UpscopeAppGroupId` key is missing from your app's `Info.plist`, the SDK will not advertise full device support on iOS, and agent requests for it are declined automatically.

By default, an agent's full device request goes straight to the system permission prompt. To show your own confirmation UI first, set `customFullDeviceRequestUI: true` in the config and **handle incoming requests** — see [Responding to full device requests](#responding-to-full-device-requests) below. With the flag set, requests stall silently unless a listener calls `respondToFullDeviceRequest`.

##### Android setup

Add the screen-capture permissions to `android/app/src/main/AndroidManifest.xml`:

```xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
```

The SDK already declares the capture service and activity. It detects these permissions at runtime — if they are missing, full device screen sharing is disabled regardless of the dashboard setting. No further configuration is required; Android shows the system screen-capture permission dialog when sharing starts.

##### Responding to full device requests

By default the SDK proceeds directly to the system permission prompt when an agent requests full device sharing. To gate the request behind your own UI, set `customFullDeviceRequestUI: true` in the config — the SDK then routes requests to your JavaScript code instead. You must listen for `fullDeviceRequest` and respond, or requests will never proceed:

```typescript
import { useEffect } from 'react';
import Upscope from '@upscopeio/react-native-sdk';

useEffect(() => {
  const subs = [
    Upscope.addListener('fullDeviceRequest', ({ requestId, agentName }) => {
      // Optionally show your own confirmation UI here (agentName is the
      // requesting agent's display name, or null), then respond:
      Upscope.respondToFullDeviceRequest(requestId, true); // false to decline
    }),

    Upscope.addListener('fullDeviceSharingStateChanged', ({ state }) => {
      console.log('Full device sharing:', state); // 'active' | 'pendingRequest' | 'inactive'
    }),
  ];

  return () => subs.forEach((s) => s.remove());
}, []);
```

After you accept, iOS shows the system broadcast picker (the user taps **Start Broadcast**) and Android shows the screen-capture dialog (the user taps **Start now**). End sharing programmatically at any time:

```typescript
Upscope.stopFullDeviceSharing();
```

The user can also stop sharing from iOS **Control Center** or the Android capture notification; the SDK reports this via `fullDeviceSharingStateChanged`.

##### Responding to control requests

Remote control requests follow the same pattern, but are opt-in: set `customControlRequestUI: true` in the config to replace the native control request prompt. The SDK then emits `controlRequest` instead of showing the prompt; respond with `respondToControlRequest` to grant or decline:

```typescript
import { useEffect } from 'react';
import Upscope from '@upscopeio/react-native-sdk';

useEffect(() => {
  const subs = [
    Upscope.addListener('controlRequest', ({ requestId, agentName }) => {
      // Optionally show your own confirmation UI here (agentName is the
      // requesting agent's display name, or null), then respond:
      Upscope.respondToControlRequest(requestId, true); // false to decline
    }),

    Upscope.addListener('remoteControlStateChanged', ({ state }) => {
      console.log('Remote control:', state); // 'active' | 'pendingRequest' | 'inactive'
    }),
  ];

  return () => subs.forEach((s) => s.remove());
}, []);
```

Revoke control programmatically at any time with `Upscope.stopRemoteControl()`; the session continues and only the agent's ability to interact stops.

##### Limitations

- Works on **physical devices** only (not the iOS Simulator or Android emulators)
- **Element masking** is not available in full device mode (the SDK cannot inspect views outside your app)
- **Remote control** only works within your app, not on the home screen or other apps
- **Drawing annotations** are not displayed in full device mode
- The user must explicitly start the broadcast/capture via the system prompt — it cannot be started programmatically

### Element Masking

Source: https://cobrowsingapi.com/docs/sdk/element-masking

Co-Browsing API allows you to mask certain elements from the Agent. When you do this, not only will the content of the masked elements be hidden from the Agent, but it will also not go through our servers.

**Javascript:**

The easiest way to mask an element is to add its CSS selector to the dashboard `Settings` » `Co-browsing`.

For example, if you want to hide an element with the id `secret-code`, you'd add `#secret-code` to the settings.

You can also mask elements by adding the `no-upscope` CSS class to them.

**Javascript (React):**

You can use the `Masked` and `NoRemoteControl` components from our React SDK to mask parts of the page.

```javascript
import { Masked } from "@upscopeio/react";

function YourComponent() {
  return (
    <div>
      <label>Your SSN</label>
      <Masked>
        <input type="text" />
      </Masked>
    </div>
  )
}
```

**iOS:**

##### UIKit

Call `addMaskedView(_:)` on the `Upscope` singleton to hide a view during screen sharing:

```swift
import UpscopeIO

class SensitiveViewController: UIViewController {
    @IBOutlet weak var ssnField: UITextField!
    @IBOutlet weak var creditCardField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        Upscope.shared.addMaskedView(ssnField)
        Upscope.shared.addMaskedView(creditCardField)
    }
}
```

To remove masking:

```swift
Upscope.shared.removeMaskedView(ssnField)
```

##### Automatic Secure Field Masking

The SDK automatically masks secure text fields (like password fields) by default. To disable this:

```swift
Upscope.shared.maskSecureTextFields = false
```

##### WebView Selective Redaction

Elements inside a `WKWebView` are redacted by CSS selector, without masking the entire WebView. This works the same way as on the web: add the selectors to the dashboard **Masked elements** setting (or the `webviewMaskedElements` initialization option) and matching elements are masked in every WebView automatically — no per-WebView code.

To redact selectors specific to one screen, in addition to the configured ones:

```swift
Upscope.shared.redactWebView(webView, selectors: ["#ssn", ".credit-card"])
```

Elements matching the selectors, plus any element with the `no-upscope` class, are masked during capture. While the page is loading, navigating, or being scrolled (any moment the SDK cannot trust the element positions), the entire WebView is masked instead (fail-closed).

Requirements: JavaScript must be enabled on the WebView (`defaultWebpagePreferences.allowsContentJavaScript`). If it is disabled, the whole WebView stays masked and a warning is logged (invalid selectors log an error). Unlike Android, there is no minimum WebView version to consider — `WKWebView` always supports document-start scripts.

Selectors are matched in the WebView's top frame only. Elements inside an iframe are never redacted, same-origin or not, so use `addMaskedView` on the WebView if an iframe can show sensitive content.

Enrolling a WebView whose page is already loaded starts selective redaction immediately, because the message-handler bridge is available to the page as soon as it is added. (On Android, enrollment takes effect on the next page load.)

Selectors configured in the team dashboard (the same Masked elements setting the web SDK uses) and the `webviewMaskedElements` initialization option are merged with the selectors passed to `redactWebView`; a locally configured list never disables dashboard masking. An invalid selector in the dashboard masks every WebView entirely (fail closed) and logs an error, so validate selectors after editing them.

To keep the SDK out of a particular WebView — one showing third-party content, for example — opt it out:

```swift
Upscope.shared.stopRedactingWebView(webView)
```

An opted-out WebView is not redacted and is not picked up again by the configured selectors, so **its content is visible to the agent**. To hide it entirely instead, mask the view itself with `Upscope.shared.addMaskedView(webView)`. Call `redactWebView` to opt it back into selective redaction.

Note: on web, masking happens at the DOM level, so masked content never leaves the device. On mobile, the SDK masks pixels after capture, before transmission.

**Android:**

##### View-Based (XML Layouts)

Call `addMaskedView()` on the `Upscope` object to hide a view during screen sharing:

```kotlin
import io.upscope.sdk.Upscope

class SensitiveActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sensitive)

        val ssnField = findViewById<EditText>(R.id.ssn_field)
        val creditCardField = findViewById<EditText>(R.id.credit_card_field)

        Upscope.addMaskedView(ssnField)
        Upscope.addMaskedView(creditCardField)
    }
}
```

To remove masking:

```kotlin
Upscope.removeMaskedView(ssnField)
```

##### Automatic Secure Input Masking

The SDK automatically masks secure input fields (like password fields) by default. To disable this:

```kotlin
Upscope.maskSecureInputs = false
```

##### WebView Selective Redaction

Redact specific elements inside a `WebView` by CSS selector, without masking the entire WebView:

```kotlin
Upscope.redactWebView(webView, listOf("#ssn", ".card-number"))
```

Elements matching the selectors, plus any element with the `no-upscope` class, are masked during capture. While the page is loading, navigating, or being scrolled (any moment the SDK cannot trust the element positions), the entire WebView is masked instead (fail-closed).

Requirements: JavaScript must be enabled on the WebView, and the device's WebView must support document-start scripts (WebView 89+). If either is missing, the whole WebView stays masked and a warning is logged (invalid selectors log an error).

Selectors are matched in the WebView's top frame only. Elements inside an iframe are never redacted, same-origin or not, so use `addMaskedView` on the WebView if an iframe can show sensitive content.

Enrolling a WebView that has already loaded a page masks the whole WebView until its next navigation, when selective redaction begins. Call `redactWebView` before `loadUrl` to have it apply from the first page.

Selectors configured in the team dashboard (the same Masked elements setting the web SDK uses) and the `webviewMaskedElements` initialization option are merged with the selectors passed to `redactWebView`; a locally configured list never disables dashboard masking. Calling `Upscope.redactWebView(webView)` with no selectors enrolls the WebView with those configured selectors alone. An invalid selector in the dashboard masks every enrolled WebView entirely (fail closed) and logs an error, so validate selectors after editing them.

To stop redacting:

```kotlin
Upscope.stopRedactingWebView(webView)
```

Call this before destroying the `WebView`, so the SDK can release its reference to it.

Note: on web, masking happens at the DOM level, so masked content never leaves the device. On mobile, the SDK masks pixels after capture, before transmission.

#### Default Masking (Web Only)

On web, Co-Browsing API will automatically mask password fields and fields that contain what looks like credit card numbers. On iOS and Android, you need to explicitly mark sensitive fields for redaction.

#### Masking Inputs or Elements

On web, you can mask either form inputs or whole HTML elements. When you mask an input, the Agent will see the value of it transformed into asterisks. This way, they can see if the Visitor is typing, but not what they are typing.

When you mask any other element, nothing contained in it will show up on the Agent side. The element will be turned into a gray box.

On mobile (iOS/Android), masked views are replaced with black rectangles that hide the content completely. Secure text fields (password fields) are masked automatically by default.

**Inline Elements (Web):**
On web, you can only mask elements that have the `display` CSS property set to `block`. This is because inline elements don't have a fixed size and could span multiple lines.

#### Agent Control

The Agent will be unable to control anything that is masked. This means they can't type for a Visitor on a masked field or click on a masked button.

You can further restrict which fields are not masked but the Agent should not be able to control in your `Settings` » `Teams` » `Co-browsing`.

### Identifying the Visitor

Source: https://cobrowsingapi.com/docs/sdk/identifying-the-visitor

There are several ways to identify the visitor on Co-Browsing API.

#### Providing Identity Information

**Javascript:**

You can provide identity information when initializing the SDK or update it later.

##### At Initialization

Provide the details with the `Upscope('init');` function if you have them from your backend:

```javascript
// Rest of the installation code...
Upscope('init', {
  identities: ['John Smith', 'acme.com'],
  uniqueId: '00032'
});
```

##### After Initialization (SPA)

Call `Upscope('init');` first, then provide the identity information with `Upscope('updateConnection');`:

```javascript
// Rest of the installation code...
Upscope('init');

// getVisitorInfo is a made up function you might have in your code
getVisitorInfo().then(visitor => {
  Upscope('updateConnection', {
    identities: [visitor.name],
    uniqueId: visitor.id
  });
});
```

**Javascript (React):**

##### With UpscopeProvider Props

Pass identity information directly to the `UpscopeProvider`:

```javascript
import { UpscopeProvider } from '@upscopeio/react';

<UpscopeProvider
  apiKey="<public_api_key>"
  enabled={true}
  uniqueId={user.id}
  identities={[user.name, user.email]}
>
  {/* Your application code here */}
</UpscopeProvider>
```

##### With the useUpscope Hook

Update identity information dynamically using the hook:

```javascript
import { useUpscope } from '@upscopeio/react';

function UserProfile() {
  const { Upscope } = useUpscope();

  useEffect(() => {
    if (user) {
      Upscope('updateConnection', {
        identities: [user.name],
        uniqueId: user.id
      });
    }
  }, [user]);

  return <div>{/* ... */}</div>;
}
```

**iOS:**

##### At Initialization

Provide identity information when initializing the SDK:

```swift
let config = UpscopeConfiguration(apiKey: "YOUR_API_KEY")
try Upscope.shared.initialize(with: config)

Upscope.shared.uniqueId = "user-123"
Upscope.shared.identities = ["John Smith", "john@example.com"]
```

##### After Initialization

Update identity information using `updateConnection()`:

```swift
// After user logs in
Upscope.shared.updateConnection(
    uniqueId: "user-123",
    identities: ["John Smith", "john@example.com"],
    metadata: ["plan": "enterprise"]
)
```

##### Clearing Identity

To clear identity on logout:

```swift
Upscope.shared.reset()
```

**Android:**

##### At Initialization

Provide identity information when initializing the SDK:

```kotlin
val config = UpscopeConfiguration.Builder("YOUR_API_KEY").build()
Upscope.initialize(this, config)

Upscope.uniqueId = "user-123"
Upscope.identities = listOf("John Smith", "john@example.com")
```

##### After Initialization

Update identity information using `updateConnection()`:

```kotlin
// After user logs in
Upscope.updateConnection(
    uniqueId = "user-123",
    identities = listOf("John Smith", "john@example.com"),
    metadata = mapOf("plan" to "enterprise")
)
```

##### Clearing Identity

To clear identity on logout:

```kotlin
Upscope.reset()
```

#### Visitor Information Details

You can provide the following bits of visitor information:

| Key              | Type                                         | Description                                                                                                                                              |
|------------------|----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `identities`     | String[]                                    | An array of strings with whatever identifying information you want to send us.                                                                          |
| `uniqueId`       | String                                      | A string with a unique id of the visitor from your database. This could be the visitor's email address.                                                |
| `tags`           | String[], each matching `/^#[A-Z-]+$/`     | An array of hashtags to filter visitors by.                                                                                                            |
| `integrationIds` | String[], each matching `/^[a-z]{3,}:.+$/` | An array of strings representing an integration name and an integration id. For example, if your app is called acmechat, and the acmechat ID for the visitor is `123`, you could pass `["acmechat:123"]`. |
| `metadata`       | Object/Dictionary                           | Custom key-value pairs for additional visitor data. |

#### Removing Identification

Any piece of identification set to `undefined` (web) or `nil`/`null` (mobile) will be ignored. If you identify visitors on the login page and they navigate to another area that doesn't have identification, we will keep the data we already have.

To explicitly remove any piece of data, set it to `null` (web) or pass `nil`/`null` (mobile). To clear all identification and start fresh, call `reset()`.

#### Logging the Visitor Out

You can log the visitor out by calling the reset function:

**Javascript:**

```javascript
Upscope('reset');
```

**Javascript (React):**

```javascript
const { reset } = useUpscope();
reset();
```

**iOS:**

```swift
Upscope.shared.reset()
```

**Android:**

```kotlin
Upscope.reset()
```

This will reset the connection and create a new visitor with a new ID.

#### Using the Watch Link

The most reliable way to identify a visitor is to use the watch link. This is a unique link to the particular browser or app session the visitor is using, generated for each visitor.

##### Browser or Visitor?

Although we use the concept of "visitor" throughout the docs, we really mean the visitor's session. If the same person logs in on two different browsers or devices, and you initiate Co-Browsing API each time with the same `uniqueId`, you'll end up with two separate visitors in Co-Browsing API.

Each watch link looks like this: `https://upscope.io/w/SHORT_ID`.

We automatically add the watch link to most of our built-in integrations, but if you are building your own, you can retrieve the link:

**Javascript:**

```javascript
Upscope('getWatchLink', link => {
  console.log(link);
});
```

**Javascript (React):**

```javascript
const { Upscope } = useUpscope();
Upscope('getWatchLink', link => {
  console.log(link);
});
```

**iOS:**

```swift
if let watchLink = Upscope.shared.watchLink {
    print(watchLink)
}
```

**Android:**

```kotlin
val watchLink = Upscope.watchLink
println(watchLink)
```

##### Authentication

The watch link is not meant to be secret. You can freely share it around, as it will still require the agent to authenticate on Co-Browsing API's dashboard to be used. If you want to use the link without authentication (or without the agent having a Co-Browsing API account), you'll need to exchange it with a secure one through the REST API.

##### Getting the Short ID

If you only need the Short ID for an integration:

**Javascript:**

```javascript
Upscope('getShortId', shortId => {
  console.log(shortId);
});
```

**Javascript (React):**

```javascript
const { shortId } = useUpscope();
console.log(shortId);
```

**iOS:**

```swift
let shortId = Upscope.shared.shortId
print(shortId ?? "none")
```

**Android:**

```kotlin
val shortId = Upscope.shortId
println(shortId)
```

#### Using the Lookup Code

Learn more about the lookup code on the [dedicated page](https://cobrowsingapi.com/docs/sdk/the-lookup-code.md).

### The Lookup Code

Source: https://cobrowsingapi.com/docs/sdk/the-lookup-code

The lookup code is the easiest way to quickly find a Visitor that is not logged in. You can show the Visitor a short code that they can read over the phone (or send through chat) to the Agent. The Agent would enter the code in the Co-Browsing API dashboard and connect to the Visitor right away.

**Forcing the use of the lookup code:**
If you want to force the Agent to use the lookup code to start a session, you can enable this in the `Settings` » `Visitor Search`. This way, they won't see the full list of visitors, but only see the one that has the lookup code they entered. We throttle the search to make sure they can't enter all the codes.

#### How to Show the Code

**Javascript:**

There are different ways to show the code, which can all be enabled in the `Settings` » `Visitor Search`.

##### Control Key

You can make a popup with the code appear by asking the Visitor to press `Ctrl` 5 times anywhere on the page. This works great as it doesn't disrupt your layout and doesn't require any setup.

##### Widget

You can show a small widget on the side of the screen which the Visitor can click to see the lookup code.

##### HTML Element

You can have Co-Browsing API replace the content of an HTML element with the lookup code. For example, you could configure the element to be `#upscope-lookup-code`, and have the following on the page:

```html
<p id="support">
  If you need any help, please read the support agent this code: <span id="upscope-lookup-code"></span>
</p>
```

**Too many visitors?:**
Make sure you aren't showing the lookup code to everyone if you have a lot of Visitors online. Because the code is short, only so many Visitors can have a unique one at a time. You can still use the HTML Element option, but only make the element appear after the Visitor does something, like clicking a support link. We only add the code once the element appears on the page.

##### Link

You can show a popup with the lookup code by creating a link to `#upscope-lookup-code`.

```html
<p id="support">
  If the support agent asks you for a code, click <a href="#upscope-lookup-code">here</a>.
</p>
```

**Javascript (React):**

Use the `useUpscope` hook to get the lookup code:

```javascript
import { useUpscope } from "@upscopeio/react";

function SupportSection() {
  const { getLookupCode } = useUpscope();
  const [code, setCode] = useState(null);

  const handleShowCode = async () => {
    const lookupCode = await getLookupCode();
    setCode(lookupCode);
  };

  return (
    <div>
      {code ? (
        <p>Your support code is: <strong>{code}</strong></p>
      ) : (
        <button onClick={handleShowCode}>Get Support Code</button>
      )}
    </div>
  );
}
```

You can also use the dashboard settings (`Settings` » `Visitor Search`) to enable automatic methods like the Control Key or Widget, which work the same as the vanilla Javascript implementation.

**iOS:**

##### Shake Gesture

By default, the SDK shows a lookup code dialog when the user shakes their device. You can customize or disable this in the configuration:

```swift
let config = UpscopeConfiguration(
    apiKey: "YOUR_API_KEY",
    enableLookupCodeOnShake: true,
    lookupCodeKeyTitle: "Support Code",
    lookupCodeKeyMessage: "Please share this code with support: {%lookupCode%}"
)
```

##### Programmatic Access

Request a lookup code and display it in your own UI:

```swift
// Request a code from the server
Upscope.shared.getLookupCode()

// Read the current code
let code = Upscope.shared.lookupCode ?? ""
```

##### Subscribe to Changes

Listen for lookup code changes using Combine:

```swift
Upscope.shared.lookupCodePublisher
    .sink { lookupCode in
        print("Lookup code: \(lookupCode ?? "none")")
    }
    .store(in: &cancellables)
```

**Android:**

##### Shake Gesture

By default, the SDK shows a lookup code dialog when the user shakes their device. You can customize or disable this in the configuration:

```kotlin
val config = UpscopeConfiguration.Builder("YOUR_API_KEY")
    .enableLookupCodeOnShake(true)
    .lookupCodeKeyTitle("Support Code")
    .lookupCodeKeyMessage("Please share this code with support: {%lookupCode%}")
    .build()
```

##### Programmatic Access

Request a lookup code and display it in your own UI:

```kotlin
// Request a code from the server
Upscope.getLookupCode()

// Read the current code
val code = Upscope.lookupCode
```

##### Subscribe to Changes

Listen for lookup code changes using StateFlow:

```kotlin
launch {
    Upscope.lookupCodeFlow.collect { lookupCode ->
        println("Lookup code: $lookupCode")
    }
}
```



## REST API

Source: https://cobrowsingapi.com/docs/rest-api

### Authentication

Source: https://cobrowsingapi.com/docs/rest-api/authentication

To use our REST API, you will need a secret API key. If the API is enabled for your account, you will find this in your Co-Browsing API dashboard. You can only copy the API key once before it is hidden. You will need to update the API key if you lose access to it.

To authenticate your requests, set a `X-Api-Key` header with your API key as the value. If authorization is unsuccessful, your request will fail.

##### Authentication Errors

```javascript
{
  "status": "error",
  "error": "invalid_api_key"
}
```

| HTTP Code | Error                  | Meaning                                                             |
|-----------|------------------------|---------------------------------------------------------------------|
| 401       | `invalid_api_key`      | The API key provided was not found / no API key was provided.      |
| 401       | `no_subscription`      | The team's subscription has now expired.                           |
| 401       | `api_not_enabled`      | The team's subscription does not include API access.               |
| 401       | `please_contact_team`  | The team's access has been restricted due to unauthorized usage.   |

### API Endpoints

Source: https://cobrowsingapi.com/docs/rest-api/api-endpoints

#### Retrieving Usage Statistics

Use this endpoint to retrieve statistics about your account's usage.

**Request**

```http
GET https://api.upscope.io/v1.4/usage
```

**Response**

```javascript
// HTTP 200
{
  "status": "ok",
  "usage": {
      "agents": {
          "last_30_days": 10,
          "today": 0
      },
      "session_counts": {
          "last_30_days": 20,
          "now": 0,
          "today": 0
      },
      "session_seconds": {
          "last_30_days": 5000,
          "today": 0
      },
      "visitors": {
          "last_24_hrs": 50000,
          "last_30_days": 50000,
          "online_now": 400
      }
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `usage.agents.last_30_days` | `integer` | Number of Agents who used Co-Browsing API in the last 30 days. |
| `usage.agents.today` | `integer` | Number of Agents who used Co-Browsing API in the last 24 hours. |
| `usage.session_counts.last_30_days` | `integer` | Number of Sessions in the last 30 days. |
| `usage.session_counts.now` | `integer` | Number of Sessions currently ongoing. |
| `usage.session_counts.today` | `integer` | Number of Sessions in the last 24 hours. |
| `usage.session_seconds.last_30_days` | `integer` | Number of Session seconds in the past 30 days. |
| `usage.session_seconds.today` | `integer` | Number of Session seconds in the past 24 hours. |
| `usage.visitors.last_24_hrs` | `integer` | Number of Visitors in the past 24 hours. |
| `usage.visitors.last_30_days` | `integer` | Number of Visitors in the past 30 days. |
| `usage.visitors.online_now` | `integer` | Number of Visitors online now. |

#### Searching for a Visitor or Retrieving a List of Visitors

Use this endpoint to search for a particular Visitor or to show a list of recently online Visitors.

**Know the Visitor's ID?:**
If you know the Visitor's ID, avoid using the search functionality and instead use the direct endpoint, which will be considerably faster.

**Need just 1 result?:**
Make sure to set `max_results` to `1`. This will be considerably faster as our API service will be making a bunch of parallel requests to all the co-browsing regions you have enabled, and by setting to 1, it can respond to you as soon as it gets the first result.

**Request**

```http
GET https://api.upscope.io/v1.4/list
```

| Parameter | Type | Description |
| --- | --- | --- |
| `search` | `string` | The search query in this format. If left empty, the latest Visitors will be returned. |
| `max_results` | `number` | The max number of results to return, up to 10. Defaults to 10. Setting this to `1` will make the search faster. |

**Response**

```javascript
// HTTP 200
{
  "status": "ok",
  "visitors": [
    {
      "short_id": "1234",
      // ...
    }
  ]
}
```

| Field | Type | Description |
| --- | --- | --- |
| `visitors` | `Visitor[]` | List of Visitors matching the query. |

#### Retrieving a Visitor

Use this endpoint to retrieve a particular Visitor.

**Request**

```http
GET https://api.upscope.io/v1.4/visitors/{visitor_id}
```

| Parameter | Type | Description |
| --- | --- | --- |
| `{visitor_id}` | `string` | The short id of the Visitor. |

**Response**

```javascript
// HTTP 200
{
  "status": "ok",
  "visitor": {
    "short_id": "1234",
    "sessions": [
      // ...
    ],
    // ...
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `visitor` | `Visitor` | The Visitor. |
| `visitor.sessions` | `Session[]` | List of the Visitor's last 50 Sessions. |

**Errors**

| Code | Description |
| --- | --- |
| `404` | The Visitor does not exist. |

#### Generating a Link to Start Screen Sharing with a Visitor

Use this endpoint to generate a secure watch link that enables whoever opens it to start a co-browsing Session with the Visitor without needing to be logged into Co-Browsing API as an Agent.

**Short lived link:**
The link returned by this endpoint expires after a few minutes (see the `expiration` field in the response). It is not meant to be stored anywhere. Ideally, the Agent will make a request to an endpoint on **your server**, which makes this request behind the scenes and **redirects** the Agent to the `watch_url` immediately. This allows you to handle failed requests by setting up a `branding.retry_url`.

Your integration should never store the secure watch link anywhere (server or browser), and instead use it as soon as possible.

**Single use link:**
The link returned by this endpoint is single use. You need to make sure any proxy does not pre-scan the URL and in doing so invalidate the link. If you can, bypass any `https://cobrowsingapi.com/session/*` from the proxy.

Sharing the link on slack or teams while testing (or copying it some clipboard history apps open) might cause a request to be made by the app which invalidates the link.

**Request**

```http
POST https://api.upscope.io/v1.4/visitors/{visitor_id}/watch_url
{
  "branding": {
    "naked": true,
  },
  "permissions": {},
  "viewer": {
    "id": "123",
    "name": "Joe Smith"
  },
  "metadata": {},
  "webhook_url": "https://example.com"
}
```

| Parameter | Type | Description |
| --- | --- | --- |
| `{visitor_id}` | `string` | The short id of the Visitor. |
| `viewer.id` | `string` | The ID of the person viewing the screen (required). |
| `viewer.name` | `string`, optional | The name of the person viewing the screen. |
| `agent.id` | `string`, optional | The ID of the Agent responsible for the Session. When set, the Agent (rather than the viewer) is the identity counted for billing and analytics. Use it when the person viewing the screen is not one of your Agents — for example when the Visitor is viewing their own Session — so each viewer is not counted as a separate Agent. |
| `agent.name` | `string`, optional | The name of the Agent responsible for the Session. |
| `branding.naked` | `false`, `true`, or `"page_only"` | If `true`, the Co-Browsing API logo will not be shown on the page. This is ideal if you want to display the page inside an iframe. Defaults to `false`. Can be set to `page_only` to remove everything, including the viewer's UI. You can then control the Session by [post-messaging the iframe](https://cobrowsingapi.com/docs/rest-api/embedded-session-control.md). |
| `branding.show_end_button` | `boolean`, optional | Whether an "End session" button should be shown on the viewer page. |
| `branding.on_end_url` | `string`, optional | If set, the viewer will be redirected to this URL after the Session ends. If not set, the window will be attempted to be closed. Defaults to `null`. |
| `branding.retry_url` | `string`, optional | If set, the visitor will be able to click a "Retry now" link that redirects to this URL if there are problems. When `null`, no button is displayed. Defaults to `null`. |
| `initial_notes` | `string`, optional | The starting state of the notes for the Session. You can use this to add a template of what they should fill out. |
| `metadata` | `Record<string, string>`, optional | A JSON-encodeable object of metadata to add to the Session object. This can be used when retrieving a list of Sessions to run your own analytics. The object should be flat, meaning values can only be strings, numbers, or booleans. |
| `permissions.allow_audio` | `boolean` | Whether the viewer should have access to audio calls (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_click` | `boolean` | Whether the viewer should have access to remote click (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_confetti` | `boolean` | Whether the viewer should have access to throw confetti (defaults to `true`). |
| `permissions.allow_console` | `boolean` | Whether the viewer should have access to remote console capabilities (defaults to `false`). |
| `permissions.allow_draw` | `boolean` | Whether the viewer should have access to drawing (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_redirect` | `boolean` | Whether the viewer should have access to remote redirect & reload features (defaults to `true`, unless disabled through the Javascript SDK). Called `allow_agent_redirect` before v1.4. |
| `permissions.allow_request_control` | `boolean` | Whether the viewer should be able to request remote control when the Visitor's SDK requires a control request (defaults to `true`). |
| `permissions.allow_request_full_tab` | `boolean` | Whether the viewer should be able to request full browser tab sharing (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_request_mode` | `boolean` | Whether the viewer should be able to request a different sharing mode, such as full screen sharing (defaults to `true`). |
| `permissions.allow_request_visitor_screen` | `boolean` | Whether the viewer should be able to ask the Visitor to share their full screen (defaults to `true`). |
| `permissions.allow_scroll` | `boolean` | Whether the viewer should have access to remote scroll (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_show_viewer_screen` | `boolean` | Whether the viewer should be able to share their screen to the Visitor (defaults to `true`). Called `allow_show_agent_screen` before v1.4. |
| `permissions.allow_type` | `boolean` | Whether the viewer should have access to remote type (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_video` | `boolean` | Whether the viewer should have access to video calls (defaults to `true`). |
| `permissions.hide_private_details` | `boolean` | Whether the Visitor private information (such as IP address) should be hidden from the viewer (defaults to `false`). |
| `permissions.keeps_session_alive` | `boolean` | Whether this viewer keeps the Session alive. Set to `false` for silent observers, so the Session ends when the last keep-alive viewer leaves (defaults to `true`). |
| `session_properties.controller` | `Array<"viewer" \| "visitor">`, optional | Which sides of the Session can start audio or video calls and request a different sharing mode. Defaults to `["viewer"]`. |
| `session_properties.record` | `boolean`, optional | Whether this Session should be recorded. Defaults to your team's session recording setting. Setting it to `true` requires the session recording feature on your plan. |
| `language` | `string` | The language to use for the interface. Defaults to the user's browser. |
| `webhook_url` | `string`, URL, optional | After the session is ended, a POST request will be made to this URL, sending the Session data as JSON. |

**Upgrading from v1.3?:**
The `agent` object was replaced by the `viewer` object, and `viewer.id` is now required. In v1.4, `agent` is instead an optional, separate object identifying the Agent billed for the Session (see above). The `permissions.allow_agent_redirect` and `permissions.allow_show_agent_screen` options were renamed to `permissions.allow_redirect` and `permissions.allow_show_viewer_screen`, and `permissions.allow_notes` was removed. Requests to the `/v1.3/` endpoints keep working with the old names.

**Response**

```javascript
HTTP 200
{
    "status": "ok",
    "watch_url": "https://cobrowsingapi.com/session/a964e00a-c685-4d54-98d4-ea0bcb154d57",
    "expiration": "2022-01-11T18:01:49.700Z"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `expiration` | `Datetime` (ISO 8601) | Time at which the link is no longer valid. |
| `watch_url` | `string` | The Secure watch URL. |

**Errors**

| Code | Description |
| --- | --- |
| `404` | The Visitor does not exist. |
| `402` | This Session would put you over your subscription limits. |
| `409` | The Visitor's browser is not supported. |

#### Generating a Viewer Token to Embed Co-Browsing

Use this endpoint to generate a viewer token instead of a watch link. The token lets you embed the co-browsing screen directly in your own application with the [viewer SDK](https://cobrowsingapi.com/docs/rest-api/viewer-sdk.md), instead of opening the hosted watch page.

Calling this endpoint counts as the intent to co-browse: the Session is created right away, so only request a token when the viewer is about to connect.

**Request**

```http
POST https://api.upscope.io/v1.4/visitors/{visitor_id}/viewer_token
{
  "permissions": {},
  "viewer": {
    "id": "123",
    "name": "Joe Smith"
  },
  "metadata": {},
  "webhook_url": "https://example.com"
}
```

| Parameter | Type | Description |
| --- | --- | --- |
| `{visitor_id}` | `string` | The short id of the Visitor. |
| `viewer.id` | `string` | The ID of the person viewing the screen (required). |
| `viewer.name` | `string`, optional | The name of the person viewing the screen. |
| `agent.id` | `string`, optional | The ID of the Agent responsible for the Session. When set, the Agent (rather than the viewer) is the identity counted for billing and analytics. Use it when the person viewing the screen is not one of your Agents — for example when the Visitor is viewing their own Session — so each viewer is not counted as a separate Agent. |
| `agent.name` | `string`, optional | The name of the Agent responsible for the Session. Also used as the viewer's display name when `viewer.name` is not set. |
| `initial_notes` | `string`, optional | The starting state of the notes for the Session. |
| `metadata` | `Record<string, string>`, optional | A JSON-encodeable object of metadata to add to the Session object. |
| `permissions.allow_audio` | `boolean` | Whether the viewer should have access to audio calls (defaults to `true`, unless disabled through the Javascript SDK). Not available on premise. |
| `permissions.allow_click` | `boolean` | Whether the viewer should have access to remote click (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_confetti` | `boolean` | Whether the viewer should have access to throw confetti (defaults to `true`). |
| `permissions.allow_console` | `boolean` | Whether the viewer should have access to remote console capabilities (defaults to `false`). |
| `permissions.allow_draw` | `boolean` | Whether the viewer should have access to drawing (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_redirect` | `boolean` | Whether the viewer should have access to remote redirect & reload features (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_request_control` | `boolean` | Whether the viewer should be able to request remote control when the Visitor's SDK requires a control request (defaults to `true`). |
| `permissions.allow_request_full_tab` | `boolean` | Whether the viewer should be able to request full browser tab sharing (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_request_mode` | `boolean` | Whether the viewer should be able to request a different sharing mode, such as full screen sharing (defaults to `true`). |
| `permissions.allow_request_visitor_screen` | `boolean` | Whether the viewer should be able to ask the Visitor to share their full screen (defaults to `true`). |
| `permissions.allow_scroll` | `boolean` | Whether the viewer should have access to remote scroll (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_show_viewer_screen` | `boolean` | Whether the viewer should be able to share their screen to the Visitor (defaults to `true`). |
| `permissions.allow_type` | `boolean` | Whether the viewer should have access to remote type (defaults to `true`, unless disabled through the Javascript SDK). |
| `permissions.allow_video` | `boolean` | Whether the viewer should have access to video calls (defaults to `true`). Not available on premise. |
| `permissions.hide_private_details` | `boolean` | Whether the Visitor private information (such as IP address) should be hidden from the viewer (defaults to `false`). |
| `permissions.keeps_session_alive` | `boolean` | Whether this viewer keeps the Session alive. Set to `false` for silent observers, so the Session ends when the last keep-alive viewer leaves (defaults to `true`). |
| `session_properties.controller` | `Array<"viewer" \| "visitor">`, optional | Which sides of the Session can start audio or video calls and request a different sharing mode. Defaults to `["viewer"]`. |
| `session_properties.record` | `boolean`, optional | Whether this Session should be recorded. Defaults to your team's session recording setting. Setting it to `true` requires the session recording feature on your plan. On premise, the recording is stored on and retrieved from your own server. |
| `webhook_url` | `string`, URL, optional | After the session is ended, a POST request will be made to this URL, sending the Session data as JSON. |

**Response**

```javascript
// HTTP 200
{
    "status": "ok",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....",
    "endpoint": "https://data--eu.upscope.io",
    "region": "eu",
    "short_id": "1234",
    "expiration": "2026-01-11T18:01:49.700Z"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `token` | `string` | The token to pass to the viewer SDK. |
| `endpoint` | `string` | The server the viewer should connect to. For on-premise accounts, this is your own on-premise endpoint. |
| `region` | `string` | The region the Visitor is connected to (`onpremise` for on-premise accounts). |
| `short_id` | `string` | The short id of the Visitor. |
| `expiration` | `Datetime` (ISO 8601) | Time at which the token is no longer valid. Use it right away. |

**Errors**

| Code | Description |
| --- | --- |
| `400` | Your on-premise endpoint or secret key is not configured (on-premise accounts only). |
| `404` | The Visitor does not exist. |
| `402` | This Session would put you over your subscription limits. |
| `409` | The Visitor's browser is not supported. |

The response fields map directly onto the viewer SDK's `ViewerProvider` props. As with watch links, the token should be requested by **your server** and handed to the browser right before connecting. See [Embedding with the Viewer SDK](https://cobrowsingapi.com/docs/rest-api/viewer-sdk.md) for how to use the token.

#### Stopping an Active Session

Use this endpoint to stop an active Session.

**Request**

```http
DELETE https://api.upscope.io/v1.4/visitors/{visitor_id}/session
```

| Parameter | Type | Description |
| --- | --- | --- |
| `{visitor_id}` | `string` | The short id of the Visitor. |

**Response**

```javascript
// HTTP 200
{
  "status": "ok"
}
```

**Errors**

| Code | Description |
| --- | --- |
| `404` | The Visitor does not exist. |

#### Deleting a Visitor from Co-Browsing API

Use this endpoint to delete a Visitor from Co-Browsing API entirely. You can only delete Visitors who are not currently online.

**Request**

```http
DELETE https://api.upscope.io/v1.4/visitors/{visitor_id}
```

| Parameter | Type | Description |
| --- | --- | --- |
| `{visitor_id}` | `string` | The short id of the Visitor. |

**Response**

```javascript
// HTTP 200
{
  "status": "ok"
}
```

**Errors**

| Code | Description |
| --- | --- |
| `404` | The Visitor does not exist. |
| `409` | The Visitor is online, or one of their Sessions is still ongoing or has a recording still processing. |

#### Deleting All Visitors from Co-Browsing API

Use this endpoint to delete all Visitors from your Co-Browsing API account. This will delete only Visitors that are offline.

**Request**

```http
DELETE https://api.upscope.io/v1.4/visitors?all=true
```

| Parameter | Type | Description |
| --- | --- | --- |
| `all` | `boolean` | Set to `true` to confirm deleting all Visitors. |

**Response**

```javascript
// HTTP 200
{
  "status": "ok"
}
```

#### Agent Lookup

Use this endpoint to lookup an agent from your Co-Browsing API account.

**You must send either the email or the external_id:**
Using the `email` it will lookup for users from your account, while using the `external_id` it will lookup for external agents from your account. If you use any other parameter rather than `email` or `external_id` it will return not found.

**Request**

```http
GET https://api.upscope.io/v1.4/agents/lookup?email=john@acme.com
```

| Parameter | Type | Description |
| --- | --- | --- |
| `email` | `string` (Only specify `email` or `external_id`) | The Agent email, this will return a HelloScreen or UserView user. |
| `external_id` | `string` (Only specify `email` or `external_id`) | The External Agent id, this will return a Co-Browsing API Agent. |

**Response**

```javascript
// HTTP 200
{
  "agent": {
    "id": "u_1234",
    // ... Agent data
  },
  "status": "ok"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `agent` | `Agent` | The Agent who started or joined the Session as an observer |
| `agent.sessions` | `Session[]` | The list of the agent's last 10 sessions. |

**Errors**

| Code | Description |
| --- | --- |
| `404` | Agent not found |

#### Retrieving a List of Sessions

Use this endpoint to retrieve a list of Sessions.

**Request**

```http
GET https://api.upscope.io/v1.4/cobrowsing_sessions/list
```

| Parameter | Type | Description |
| --- | --- | --- |
| `before_id` | `string`, optional | Only list Sessions that happened before the Session with the specified id (used for pagination) |
| `after_id` | `string`, optional | Only list Sessions that happened after the Session with the specified id (used for pagination) |
| `ongoing_only` | `boolean` (defaults to `false`) | Only list Sessions that are currently active. |
| `updated_after` | `Datetime`, optional | Only list Sessions that have had some sort of update to their record after this time. |

**Response**

```javascript
// HTTP 200
{
    "status": "ok",
    "cobrowsing_sessions": [
      {
        id: "s_123",
		// ... Session details
      }
	],
    "has_more": true,
    "last_id": "s_456"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `cobrowsing_sessions` | `Session[]` | A list of Sessions, sorted by start date, from first to last. |
| `has_more` | `boolean` | Whether there are more Sessions matching your filter. (If so, paginate by changing the `after_id`) |
| `last_id` | `string \| null` | The id of the last Session. This is always the same as the last Session from the array, and can be used as a cursor with `after_id`. |

#### Authorizing an On-Premise Session

Use this endpoint to authorize an on-premise Session.

**Using our Dashboard?:**
If you have integrated your on-premise version of Co-Browsing API to our dashboard, you don't need to use the API. It all just works.

**Request**

```http
POST https://api.upscope.io/v1.4/onpremise/token
{
  "visitor_id": "VISITOR123",
  "agent": {
    "id": "123",
    "name": "Joe Smith"
  }
}
```

| Parameter | Type | Description |
| --- | --- | --- |
| `agent.id` | `string` | The ID of the Agent (required). |
| `agent.name` | `string`, optional | The name of the Agent. |
| `visitor_id` | `string` | The ID of the Visitor you want to co-browse with. |

**Response**

```javascript
// HTTP 200
{
    "authorized": true,
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkw...."
}
```

| Field | Type | Description |
| --- | --- | --- |
| `authorized` | `boolean` | `true` to indicate the Session was authorized. |
| `token` | `string` | The `token` to send to the on-premise version of Co-Browsing API. |

**Errors**

| Code | Description |
| --- | --- |
| `402` | This session was not authorized. |

### Object Types

Source: https://cobrowsingapi.com/docs/rest-api/object-types

#### Visitor

A Visitor represents a person connected through the Co-Browsing API SDK who is available to co-browse with.

**Attributes**

| Attribute name              | Type                          | Description                                                                                       |
|-----------------------------|-------------------------------|---------------------------------------------------------------------------------------------------|
| `added_at`                  | `Datetime` (ISO 8601)         | The first time the Visitor was seen.                                                             |
| `api_key`                   | `string`                      | Your public API key.                                                                              |
| `browser_name`              | `string \| null`              | The name of the browser used by the Visitor (e.g. `Mobile Safari`). Always null for non-web SDKs. |
| `call_name`                 | `string \| null`              | The call identification (used for the Agent's personal link).                                     |
| `device_name`               | `string \| null`              | The name or brand of the device (e.g. iPhone). Always null for non-web SDKs.                      |
| `device_type`               | `string \| null`              | The category of device the Visitor is connecting from. It will be one of `desktop`, `console`, `mobile`, `tablet`, `smarttv`, `wearable`, `embedded`. Always null for non-web SDKs. |
| `email`                     | `string \| null`              | The Visitor's email.                                                                              |
| `identities`                | `string[]`                    | A list of `strings` used to identify the Visitor.                                                 |
| `integration`               | `string \| null`              | The auto integration we have performed on this Visitor. This will be set to the live chat system you have installed on the page last visited by the Visitor. |
| `ip_address`                | `string`                      | The IP address the Visitor last connected from.                                                   |
| `is_in_session`             | `boolean`                     | A boolean describing whether the Visitor is currently screen sharing with someone.                |
| `is_online`                 | `boolean`                     | A boolean describing whether the Visitor is currently connected to Co-Browsing API (and available for screensharing). |
| `is_supported`              | `boolean`                     | A boolean describing whether the Visitor's browser supports Co-Browsing API.                             |
| `is_waiting_for_call`       | `boolean`                     | A boolean describing if the Visitor is waiting for a call from the agent.                         |
| `js_configuration`          | `boolean`                     | A boolean describing whether the Visitor has configured Co-Browsing API through the Javascript API in a way that could conflict with the account's general settings. This is also set to true if configured through mobile SDKs public APIs. |
| `last_seen_at`              | `Datetime` (ISO 8601)         | A timestamp of the last time the Visitor was seen on the website.                                 |
| `last_url`                  | `string \| null` (Only for web SDK) | The last URL the Visitor has visited.                                                             |
| `location_city`             | `string \| null`              | The city the Visitor is in (according to their IP address). If the city is not known, this will be null. |
| `location_country_name`     | `string \| null`              | The country (name) the Visitor is in (according to their IP address). If the country is not known, this will be null. |
| `location_country`          | `string \| null`              | The country (code) the Visitor is in (according to their IP address). If the country is not known, this will be null. |
| `lookup_code`               | `string \| null`              | The code your agent can search for to quickly find the Visitor in the interface. If not set, it will be null. |
| `name`                      | `string \| null`              | It will return the first item from `identities` that looks like a name.                           |
| `nickname`                  | `string`                      | It will return the Visitor's name, the first part of the Visitor's email, or the Visitor's ID.    |
| `short_id`                  | `string`                      | The Co-Browsing API ID of the Visitor.                                                                   |
| `tags`                      | `string[]`                    | The value provided in the `tags`.                                                                 |
| `unique_id`                 | `string \| null`              | The unique ID used to identify the Visitor. If this was not set, it will be null.                 |
| `watch_link`                | `string`                      | A public URL used to start a session with the Visitor.                                            |
| `sdk`                       | `string`                      | Either `web`, `ios` or `android`.                                                                 |

#### Session

A Session is a co-browsing interaction between one or more Agents and a Visitor.

**Call vs Session:**
Throughout the docs, you'll see the concept of Calls and Sessions. Sessions can happen with or without Calls, which are the audio component of a Session.

**Attributes**

| Attribute name              | Type                          | Description                                                                                       |
|-----------------------------|-------------------------------|---------------------------------------------------------------------------------------------------|
| `agents`                    | `Agent[]`                     | The collection of agents that joined the Session.                                                 |
| `agent_notes`               | `Array<{text: string, by: string}>` | The notes taken by the agents. `by` will be the `id` of the agent.                                |
| `call_length_seconds`       | `integer`                     | The duration of the audio call in seconds.                                                        |
| `call_name`                 | `string \| null`              | The username of the Agent that initiated the Session through their personal link.                 |
| `ended_at`                  | `Datetime` (ISO 8601)         | The datetime of when the Session ended.                                                           |
| `features_used`             | `string[]`                    | The collection of features used (e.g `["remote_typing"]`).                                        |
| `formatted_call_length`     | `string`                      | The duration of the audio call formatted (e.g `"00:11"`).                                         |
| `formatted_length`          | `string`                      | The duration of the Session formatted (e.g `"01:10"`).                                            |
| `metadata`                  | `Record<string, string>`      | Metadata from the Session creation, plus the metadata from the Visitor object with the keys prefixed with `visitor_`. |
| `quality_feedback`          | `Array<{rating: number, feedback: string \| null, by: string}>` | The feedback by the Visitor and the Agents on the quality of the Session. `by` will be the `id` of the agent. |
| `visitor_agent_feedback`    | `string \| null`              | The rating feedback by the Visitor for the Agent. This is only present if there is a single Agent. |
| `visitor_agent_rating`      | `number \| null`              | The rating left by the Visitor for the Agent. This is only present if there is a single Agent.    |
| `visitor_email`             | `string \| null`              | The email for the Visitor.                                                                        |
| `visitor_ip_address`        | `string \| null`              | The IP address for the Visitor (always present unless the Visitor data has been deleted).         |
| `visitor_unique_id`         | `string \| null`              | The unique ID for the Visitor.                                                                    |
| `id`                        | `string`                      | The ID of the Session.                                                                            |
| `length_seconds`            | `integer`                     | The duration of the Session in seconds.                                                           |
| `on_premise`                | `boolean`                     | A boolean describing whether the Session happened on an on-premise version of Co-Browsing API.           |
| `public_video_url`          | `string \| null`              | A public URL for the recording of the Session, if one is available, which requires the viewer to be authenticated into Co-Browsing API. |
| `secret_video_url`          | `string \| null`              | A secret URL pointing directly to the video recording of the Session, if one is available.        |
| `session_authorized`        | `boolean`                     | A boolean describing whether the Session was authorized by the Visitor.                           |
| `source`                    | `string \| null`              | A number describing the source of the Session. One of: `api`, `search`, `lookup code`, `personal link`, `agent request`, `integration`. |
| `start_url`                 | `string \| null` (will be `null` for mobile SDKs) | The URL the Visitor was at when the session started.                                              |
| `started_at`                | `Datetime` (ISO 8601)         | The datetime of when the Session started.                                                         |
| `video_status`              | `string \| null` (will be `null` if video recording is not enabled) | The status of the Session's video recording. One of `pending`, `recording`, `not_available`, `ready`, `expired`, `removed`. |
| `visitor_id`                | `string`                      | The ID of the Visitor.                                                                            |l; status_code: number \| null; timed_out: boolean \| null; will_retry_at: Date \| null; }>` | A log of attempted webhook deliveries.                                                            |
| `went_live`                 | `boolean`                     | A boolean describing whether the Session actually started.                                        |

#### Connection

A Connection represents a single active visitor connection — for example, an open browser tab or a mobile app instance. A visitor can have more than one connection at a time. Connection objects are camelCase and are only available in the [embedded session state](https://cobrowsingapi.com/docs/rest-api/embedded-session-control.md#session-state).

**Attributes**

| Attribute name | Type | Description |
|---|---|---|
| `uniqueConnectionId` | `string` | Unique ID for this connection. Used with the `setActiveConnection` instruction to switch to it. |
| `currentUrl` | `string \| null` | The URL currently open in this connection. |
| `activeSince` | `string` (ISO 8601) | When this connection became active. |
| `status` | `"active" \| "disconnected"` | Whether this connection is currently active. |
| `expectedDisconnect` | `{ returnTimeSeconds: number \| null; message: string \| null; title: string \| null; ts: number } \| null` | If set, the visitor is expected to disconnect temporarily (e.g. navigating to another page). `returnTimeSeconds` is how long until they return, or `null` if unknown. |
| `hasFocus` | `boolean` | Whether this connection's tab or window currently has focus. |
| `lastFocusAt` | `Date \| null` | When this connection last had focus. |
| `screenWidth` | `number` | Screen width in pixels. |
| `screenHeight` | `number` | Screen height in pixels. |
| `audioSupported` | `boolean` | Whether this connection supports audio. |
| `fullTabStatus` | `"off" \| "authorizing" \| "active"` | The current full-tab sharing state for this connection. |
| `requireAuthorizationForSession` | `boolean` | Whether the visitor must authorize before the session can start. |
| `requireControlRequest` | `boolean` | Whether the agent must request control before interacting. |
| `allowRemoteConsole` | `boolean` | Whether the agent can run console commands. |
| `allowAgentRedirect` | `boolean` | Whether the agent can redirect the visitor's browser. |
| `allowRemoteClick` | `boolean` | Whether the agent can click on the visitor's screen. |
| `allowRemoteScroll` | `boolean` | Whether the agent can scroll on the visitor's screen. |
| `allowRemoteType` | `boolean` | Whether the agent can type on the visitor's screen. |
| `allowFullScreen` | `boolean` | Whether full-screen sharing is allowed. |
| `allowRequestFullTab` | `boolean` | Whether full-tab sharing is allowed. |
| `cobrowsingVideoEnabled` | `"two_way" \| "visitor_to_agent" \| "agent_to_visitor" \| "disabled"` | The video sharing configuration for this connection. |
| `cobrowsingVideoAudioRelationship` | `"independent" \| "video_requires_audio" \| "automatic"` | How audio and video relate to each other. `video_requires_audio` — video can only be enabled when audio is active; `automatic` — audio starts automatically with video. |
| `enableSessionRating` | `boolean` | Whether the visitor is prompted to rate the session. |
| `agentPrompt` | `string \| null` | A custom message shown to the agent at the start of the session. |
| `assetsProxyConfigured` | `boolean` | Whether an assets proxy is configured for this connection. |

#### Agent

An Agent is someone who started or joined the Session as an observer.

**Attributes**

| Attribute name              | Type                          | Description                                                                                       |
|-----------------------------|-------------------------------|---------------------------------------------------------------------------------------------------|
| `agent_nickname`            | `string`                      | The Agent's nickname.                                                                             |
| `email`                     | `string \| null`              | The Agent's email (always `null` for Co-Browsing API agents, always present otherwise).           |
| `external_id`               | `string \| null`              | If generated through the REST API, the ID provided by you (always `null` for HelloScreen or UserView users, always present otherwise). |
| `id`                        | `string`                      | The Agent's id within Co-Browsing API (`u_${string}` for HelloScreen or UserView users or `ea_${string}` for Co-Browsing API agents).  |
| `name`                      | `string`                      | The Agent's name.                                                                                 |
| `on_premise`                | `boolean`                     | A boolean describing whether the agent is on premise.                                             |

### Better Searching

Source: https://cobrowsingapi.com/docs/rest-api/better-searching

The Visitor searching endpoint works with any type of text and will perform a fuzzy search through all your Visitors. We also have special tokens that make the search faster and more accurate.

##### Search Formats and Examples

| **Format**                                                               | **Example**               | **Index Used**                                                                                          |
|-------------------------------------------------------------------------|---------------------------|---------------------------------------------------------------------------------------------------------|
| Any email                                                               | `john@example.com`        | Any email [provided](https://cobrowsingapi.com/docs/sdk/identifying-the-visitor.md) through `uniqueId` or `identities`.                                         |
| `online`                                                                | `online`                  | Filters online visitors only.                                                                           |
| `-online`                                                               | `-online`                 | Filters offline visitors only.                                                                          |
| `ip:` + the IP                                                          | `ip:1.1.1.1`              | Shows visitors with IP `1.1.1.1`.                                                                       |
| `id:` + the ID                                                          | `id:user123`              | Shows visitors with `uniqueId` equal to `user123`.                                                      |
| A four-digit number                                                     | `1234`                    | Shows visitors with the `1234` lookup code.                                                             |
| Any tags                                                                | `#client`                 | Shows visitors with the `#client` tag.                                                                  |
| `integration_id:` + integration name + `:` + integration id             | `integration_id:acmechat:1234` | Shows visitors with integration id `acmechat` equal to `1234`.                                         |

**Searching for One?:**
If you are only searching for a single Visitor, make sure you set `max_results` to `1` to make the search faster. It will return as soon as one of our datacenters finds something, and they tend to respond more quickly when they do find a Visitor in their database.


### Embedded Session Control

Source: https://cobrowsingapi.com/docs/rest-api/embedded-session-control

When you generate a secure watch link through the REST API, you have the option of setting the `branding.naked` parameter to "page_only", which means no UI is shown to the Agent. The generated link should ideally be opened in an iframe you control. This setup allows you to listen for events and control the session by sending and receiving messages to the iframe.

#### Communicating with the iframe

To listen for updates to the session status, add an event listener to the iframe's parent frame (i.e., your page) and filter messages that come from the iframe. You can post messages to the iframe's window to control the session.

**Listening for events:**

If you have embedded Co-Browsing API within your application like this:

```html
<iframe src="https://acmechat.com/cobrowse?id=VISITOR123" id="upscope-iframe"></iframe>
```

You can start listening for events by doing:

```javascript
const upscopeWindow = document.getElementById('upscope-iframe').contentWindow;
window.addEventListener('message', event => {
  if (!event.source || event.source !== upscopeWindow)
    return;
  const {data} = event;
  // ... deal with the Co-Browsing API event
  console.log('Co-Browsing API event type', data.type);
});
```

**Sending instructions:**

If you have embedded Co-Browsing API within your application like this:

```html
<iframe src="https://acmechat.com/cobrowse?id=VISITOR123" id="upscope-iframe"></iframe>
```

You can send instructions by doing:

```javascript
const upscopeWindow = document.getElementById('upscope-iframe').contentWindow;
upscopeWindow.postMessage({
  action: "name",
  // ... other parameters
}, '*');
```

#### Events

Events will be received in this format:

```javascript
{
  "type": "eventType",
  // ...parameters
}
```

The following events are sent by the iframe to the parent frame:

| Type                        | Parameters                                           | Description                                                                                                     |
|-----------------------------|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| `upscope.sessionState.update` | `state: EmbeddedSessionState`                        | Fired whenever the session state changes. Contains a full snapshot — see [Session State](#session-state) below. This is the primary event to listen for. |
| `sessionEnded`              | `reason: "ended" \| "error"`                         | Fired when the session is terminated. `ended` — session ended normally; `error` — session ended due to an error (see the `error` event for details). |
| `customMessage`             | `message: Record&lt;string, unknown&gt;`                   | Fired when the visitor's SDK sends a custom message via `Upscope('sendCustomMessage', ...)`.                    |
| `error`                     | `error: string`                                      | Fired when a connection or remote control error occurs. Possible values: `connection:authentication`, `connection:data`, `connection:user_not_found`, `connection:user_not_online`, `connection:expired`, `connection:version_not_supported`, `remote_control:inactive`, `remote_control:element_not_allowed`, `remote_control:click_not_allowed`, `remote_control:type_not_allowed`, `remote_control:scroll_not_allowed`. |

#### Session State

Every `upscope.sessionState.update` event carries an `EmbeddedSessionState` object — a full snapshot of everything happening in the session at that moment.

| Property | Type | Description |
|---|---|---|
| `activeConnectionId` | `string \| undefined` | The ID of the visitor connection currently being viewed. A visitor can have multiple connections (e.g. multiple open tabs). Use `setActiveConnection` to switch between them. |
| `agentIdleness` | `"active" \| "willDisconnect" \| "disconnected"` | The activity state of the agent. `willDisconnect` — the agent will disconnect shortly due to inactivity. |
| `audioState` | `"off" \| "ringing" \| "accepted" \| "authorizing" \| "active"` | The current audio call state. |
| `availableModes` | `("visitor_browser" \| "visitor_screen" \| "agent_screen")[]` | The sharing modes available in the current session. |
| `color` | `string` | The current active color for the agent drawing and pointer tools. |
| `connected` | `boolean` | Whether the session is currently connected. |
| `connections` | [`Connection&lsqb;&rsqb;`](https://cobrowsingapi.com/docs/rest-api/object-types.md#connection) | All active visitor connections (browser tabs or app instances). Use alongside `activeConnectionId` to let the agent switch between them. |
| `controlStatus` | `"off" \| "requested" \| "granted" \| undefined` | The remote control status. `off` — the agent does not have control; `requested` — the agent has requested control and is waiting for the visitor to accept; `granted` — the agent has control. `undefined` if the session has not started. |
| `controlTool` | `"cursor" \| "drawing" \| "pointer" \| null` | The currently active agent control tool, or `null` if none is selected. |
| `currentUrl` | `string \| null` | The current URL of the active visitor connection. |
| `dataBounceSpeed` | `number` | Round-trip data latency in milliseconds. Values above 500ms indicate a slow connection. |
| `isVideoOn` | `boolean` | Whether video streaming is currently active. |
| `mode` | `"visitor_browser" \| "visitor_screen" \| "agent_screen" \| "other_agent_screen" \| undefined` | The current sharing mode. `visitor_browser` — viewing the visitor's browser tab; `visitor_screen` — viewing the visitor's full screen; `agent_screen` — this agent is sharing their screen; `other_agent_screen` — another agent is sharing their screen; `undefined` — session has not started. |
| `muted` | `boolean` | Whether the agent's audio is muted. |
| `observersCount` | `number` | The number of agents currently observing the session. |
| `sessionStatus` | `"waiting" \| "pendingRequest" \| "active" \| "snapshotReceived" \| "viewerReady" \| "terminated"` | The current session status. `waiting` — waiting for the browser to respond; `pendingRequest` — waiting for the visitor to accept; `active` — session has started; `snapshotReceived` — first frame received; `viewerReady` — **the page is visible**; `terminated` — the session has ended. |
| `size` | `{ width: number; height: number } \| undefined` | The current dimensions of the viewer in pixels. |
| `videoAgentState` | `"off" \| "authorizing:&lt;observerId&gt;" \| "active:&lt;observerId&gt;"` | Video state from the agent side. The suffix after `:` is the observer ID. |
| `videoVisitorState` | `"off" \| "ringing" \| "authorizing:&lt;connectionId&gt;" \| "active:&lt;connectionId&gt;"` | Video state from the visitor side. `ringing` — incoming video call; the suffix after `:` is the visitor's connection ID. |
| `viewerAudioAvailable` | `boolean` | Whether audio is available on the agent side. |
| `visitor` | [`Visitor?`](https://cobrowsingapi.com/docs/rest-api/object-types.md#visitor) | The visitor in the session. Property names use camelCase rather than snake_case. `undefined` if the session has not started. |
| `visitorAudioAvailable` | `boolean` | Whether audio is available on the visitor's side. |

#### Instructions

Instructions need to be sent in this format:

```javascript
{
  "action": "actionName",
  // ... parameters
}
```

The following instructions are available:

| Action               | Parameters                               | Description                                                                 |
|----------------------|------------------------------------------|-----------------------------------------------------------------------------|
| `endSession`         | —                                        | Terminates the session.                                                     |
| `setMode`            | `mode: "draw" \| "pointer" \| "cursor" \| null` | Sets which mode the Agent is currently using. Allowed values are: `draw` — Drawing mode; `pointer` — Pointer (spotlight) mode; `cursor` — Cursor mode; `null` — No tool selected. |
| `switchColor`        | `color: string?`                        | Changes the current Agent color. If `color` is not set, it will toggle between built-in colors. |
| `clearDrawings`      | —                                        | Clears all drawings from the Visitor's screen. Requires the `allow_draw` permission. |
| `toggleAudio`        | `on: boolean`                           | Toggles the audio state.                                                   |
| `toggleVideo`        | —                                        | Toggles the video state.                                                   |
| `setMuted`           | `muted: boolean`                        | Sets the muted state for audio.                                            |
| `beginVideo`         | —                                        | Begins video streaming.                                                    |
| `endVideo`           | —                                        | Ends video streaming.                                                      |
| `forceRefresh`       | —                                        | Forces a refresh of the session.                                           |
| `historyBack`        | —                                        | Navigates back in the session history.                                     |
| `historyForward`     | —                                        | Navigates forward in the session history.                                  |
| `reloadPage`         | —                                        | Reloads the current page in the session.                                   |
| `requestControl`     | —                                        | Requests control of the session.                                           |
| `requestFullTab`     | —                                        | Requests full tab control.                                                 |
| `runConsoleCommand`  | `command: string`                       | Runs a console command in the session.                                     |
| `sendCustomMessage`  | `message: Record&lt;string, unknown&gt;`      | Sends a custom message to the session.                                     |
| `setActiveConnection` | `connectionId: string`                 | Switches the active visitor connection (e.g. to a different browser tab). Use `activeConnectionId` and `connections` from `EmbeddedSessionState` to get available connection IDs. |
| `setCurrentUrl`      | `url: string`                           | Sets the current URL in the session.                                       |
| `stopFullTab`        | —                                        | Stops full tab control.                                                    |
| `emojiConfetti`      | `emoji: string`                         | Triggers an emoji confetti effect using the specified emoji.               |

### Embedding with the Viewer SDK

Source: https://cobrowsingapi.com/docs/rest-api/viewer-sdk

Instead of opening a hosted watch link (or embedding it in an iframe), you can render the co-browsing screen natively inside your own React application with the [`@upscopeio/viewer-sdk`](https://www.npmjs.com/package/@upscopeio/viewer-sdk) package. This gives you full control over the UI around the Visitor's screen: you build the toolbar, the buttons, and the layout, and the SDK provides the screen itself plus hooks to control the Session.

Use this when you want co-browsing to feel like a native part of your product. If you just need to open a session quickly, the [secure watch link](https://cobrowsingapi.com/docs/rest-api/api-endpoints.md#generating-a-link-to-start-screen-sharing-with-a-visitor) or [embedded session control](https://cobrowsingapi.com/docs/rest-api/embedded-session-control.md) are simpler options.

#### Installation

```sh
npm install @upscopeio/viewer-sdk
```

React 18 (`react` and `react-dom`) are peer dependencies.

#### Getting a Viewer Token

The SDK authenticates with a viewer token generated by the [viewer token endpoint](https://cobrowsingapi.com/docs/rest-api/api-endpoints.md#generating-a-viewer-token-to-embed-co-browsing). The token must be requested by **your server** (the REST API key must never reach the browser) and handed to the browser right before connecting: tokens are short lived, and requesting one creates the Session.

```javascript
// On your server
const response = await fetch(`https://api.upscope.io/v1.4/visitors/${visitorId}/viewer_token`, {
  body: JSON.stringify({
    viewer: { id: "123", name: "Joe Smith" },
  }),
  headers: {
    "Authorization": `Bearer ${UPSCOPE_API_KEY}`,
    "Content-Type": "application/json",
  },
  method: "POST",
});

// Pass token, endpoint, region and short_id to your frontend
const { endpoint, region, short_id, token } = await response.json();
```

The response fields map directly onto the `ViewerProvider` props below. For on-premise accounts, `endpoint` points at your own on-premise server.

#### Rendering the Viewer

Wrap your viewer UI in a `ViewerProvider`, and render the Visitor's screen with the `Content` component:

```tsx
import { Content, ViewerProvider, useUpscopeViewer } from "@upscopeio/viewer-sdk";

function CobrowsingViewer({ endpoint, region, shortId, token }) {
  return (
    <ViewerProvider endpoint={endpoint} idleDisconnectSeconds={600} region={region} shortId={shortId} token={token}>
      <Screen />
    </ViewerProvider>
  );
}

function Screen() {
  const { connected, endSession, sessionStatus } = useUpscopeViewer();

  return (
    <div>
      {sessionStatus !== "active" && <p>Waiting for the visitor to accept...</p>}
      <Content color="red" controlTool="cursor" maxHeight={1200} maxWidth={1200} />
      {connected && endSession && <button onClick={() => endSession()}>End session</button>}
    </div>
  );
}
```

##### `ViewerProvider` Props

| Prop | Type | Description |
| --- | --- | --- |
| `token` | `string` | The `token` from the viewer token endpoint. |
| `shortId` | `string` | The Visitor's short id (`short_id` in the response). |
| `region` | `string` | The region the Visitor is connected to (`region` in the response). |
| `endpoint` | `string`, optional | The server to connect to (`endpoint` in the response). Required for on-premise accounts; on cloud accounts it can be omitted and the connection is derived from `region`. |
| `idleDisconnectSeconds` | `number \| null` | Disconnect the viewer after this many seconds without interaction. Pass `null` to never disconnect for idleness. |

##### `Content` Props

`Content` renders the Visitor's screen on a canvas and forwards the selected control tool's interactions (clicks, scrolling, typing, drawing) to the Visitor.

| Prop | Type | Description |
| --- | --- | --- |
| `color` | `string` | The color used for this viewer's cursor, drawings, and pointer. |
| `controlTool` | `"cursor" \| "drawing" \| "pointer" \| null` | The active tool. `cursor` remote-controls the page, `drawing` draws on it, `pointer` highlights without interacting. `null` disables interaction. |
| `maxWidth` | `number` | Maximum width of the rendered screen, in pixels. |
| `maxHeight` | `number` | Maximum height of the rendered screen, in pixels. |
| `activeConnectionId` | `string \| null`, optional | Which of the Visitor's connections (browser tabs or devices) to display. Defaults to the most recently active one. |
| `zoomAreaLabel` | `string`, optional | Accessibility label for the zoom area. |

#### Controlling the Session

##### `useUpscopeViewer()`

Returns Session-level state and actions. Action functions are `undefined` while unavailable (for example before the Session is active), so you can use their presence to enable or disable UI.

| Field | Type | Description |
| --- | --- | --- |
| `connecting` | `boolean` | Whether the SDK is connecting to the server. |
| `connected` | `boolean` | Whether the SDK is connected to the server. |
| `sessionStatus` | `"waiting" \| "pendingRequest" \| "active" \| undefined` | The Session status: waiting for the Visitor, waiting for the Visitor to accept, or active. |
| `loadingStagesLeft` | `("accept" \| "connect" \| "load" \| "firstFrame")[]` | The steps left before the Visitor's screen renders. Empty when the screen is visible; useful for progress UI. |
| `endSession` | `(() => void) \| undefined` | Ends the Session. |
| `visitor` | `object \| undefined` | The Visitor being observed, with fields like `shortId`, `identities`, `metadata`, `deviceType`, `browserName`, `locationCountry`, and `isOnline`. |
| `connections` | `object[]` | The Visitor's open connections (browser tabs or devices). Each has a `uniqueConnectionId`, the `currentUrl`, a `status` (`"active"` or `"disconnected"`), and that connection's SDK configuration. |
| `latestActiveConnectionId` | `string \| undefined` | The id of the Visitor's most recently active connection. |
| `viewers` | `object[]` | The viewers connected to the Session, each with a `connectionId`, `name`, `externalId`, screen size, and audio status. |
| `viewersCount` | `number` | The number of viewers connected to the Session. |
| `mode` | `string \| undefined` | The current sharing mode: `"visitor_browser"` (co-browsing), `"visitor_screen"` (full screen sharing), `"visitor_app"` (mobile app), `"own_viewer_screen"` (you are sharing your screen), or `"other_viewer_screen"` (another viewer is sharing theirs). |
| `availableModes` | `string[]` | The sharing modes the Session can switch to (same values as `mode`). |
| `changeMode` | `(mode) => void` | Requests a switch to another sharing mode. Requires the `allow_request_mode` permission. |
| `agentIdleness` | `"active" \| "willDisconnect" \| "disconnected"` | The idle-disconnect state driven by `idleDisconnectSeconds`. Use `willDisconnect` to warn the viewer before they are disconnected. |
| `forceRefresh` | `(() => void) \| undefined` | Requests a full refresh of the Visitor's screen data. |
| `waitingForceRefresh` | `boolean` | Whether a forced refresh is in progress. |
| `expectedDisconnectInfo` | `object \| undefined` | Present when the active connection is expected to disconnect briefly (for example, the Visitor is navigating to another page). Contains an optional `title` and `message` to show, and `returnTimeSeconds` when the Visitor is expected back. |
| `sendCustomMessage` | `(message: object) => void` | Sends a JSON message to the Visitor's SDK. |
| `listen` | `((event, handler) => void) \| undefined` | Subscribes to low-level Session events from the server connection. |
| `setErrorHandler` | `(handler: (error: ViewerError) => void) => void` | Registers the error handler (see [Handling Errors](#handling-errors)). |
| `dataBounceSpeed` | `number` | The latest measured round-trip time to the Visitor, in milliseconds. |

##### `useUpscopeConnection(connectionId?)`

Returns state and actions for one of the Visitor's connections (defaulting to the most recently active one). Like above, each action is `undefined` whenever it is unavailable — because of the Session mode, the SDK configuration, or the [permissions the token was generated with](https://cobrowsingapi.com/docs/rest-api/api-endpoints.md#generating-a-viewer-token-to-embed-co-browsing).

| Field | Type | Description |
| --- | --- | --- |
| `currentUrl` | `string \| null` | The URL the Visitor is currently on. |
| `size` | `{ width, height } \| undefined` | The size of the shared screen. |
| `setCurrentUrl` | `((url: string) => void) \| undefined` | Redirects the Visitor to another URL. Requires the `allow_redirect` permission. |
| `reloadPage` | `(() => void) \| undefined` | Reloads the Visitor's page. Requires the `allow_redirect` permission. |
| `historyBack` | `(() => void) \| undefined` | Navigates the Visitor's browser history back. Requires the `allow_redirect` permission. |
| `historyForward` | `(() => void) \| undefined` | Navigates the Visitor's browser history forward. Requires the `allow_redirect` permission. |
| `requestControl` | `(() => void) \| undefined` | Asks the Visitor for remote control. Only defined when the Visitor's SDK is configured to require a control request and control is off. |
| `requestFullTab` | `(() => void) \| undefined` | Asks the Visitor to share their full browser tab, when parts of the page (like cross-origin iframes) cannot be co-browsed. Requires the `allow_request_full_tab` permission. |
| `stopFullTab` | `(() => void) \| undefined` | Stops full tab sharing and returns to co-browsing. |
| `runConsoleCommand` | `((command: string) => void) \| undefined` | Runs a command in the Visitor's console. Requires the `allow_console` permission. |
| `consoleLogs` | `object[]` | The Visitor's console output, when remote console is enabled. Each entry has an `id`, a `timestamp`, a `level` (`"error"`, `"warn"`, `"info"`, `"log"`, `"debug"`, `"input"`, or `"output"`), the `message`, and an optional `stack`. |
| `confetti` | `(() => void) \| undefined` | Throws confetti on the Visitor's screen. |
| `emojiConfetti` | `((emoji: string) => void) \| undefined` | Throws emoji confetti on the Visitor's screen. |
| `clearDrawings` | `(() => void) \| undefined` | Clears all drawings from the Visitor's screen. Requires the `allow_draw` permission. |

Remote control actions require the Visitor to have granted control; when control is inactive, calling them triggers the `remote_control:inactive` error instead.

#### Handling Errors

Register an error handler to react to connection and remote control problems:

```tsx
const { setErrorHandler } = useUpscopeViewer();

useEffect(() => {
  setErrorHandler((error) => {
    if (error === "connection:expired") {
      // Fetch a new viewer token and remount the ViewerProvider
    }
  });
}, [setErrorHandler]);
```

| Error | Description |
| --- | --- |
| `connection:authentication` | The token is invalid. |
| `connection:expired` | The token has expired. Generate a new one. |
| `connection:user_not_found` | The Visitor does not exist. |
| `connection:user_not_online` | The Visitor is not online. |
| `connection:version_not_supported` | The SDK version is not supported by the server. |
| `connection:data` | The connection received invalid data. |
| `remote_control:inactive` | A remote control action was attempted while the viewer does not have control. |
| `remote_control:click_not_allowed` | Remote click is not allowed. |
| `remote_control:scroll_not_allowed` | Remote scroll is not allowed. |
| `remote_control:type_not_allowed` | Remote type is not allowed. |
| `remote_control:element_not_allowed` | The element is blocked from remote control by the Visitor's SDK configuration. |

#### Audio and Video Calls

Audio and video calls are managed by the `AudioProvider` and `VideoProvider` components. Nest them inside the `ViewerProvider` and use the `useAudio` and `useVideo` hooks from components inside them.

Calls are subject to the permissions the token was generated with (`allow_audio` and `allow_video`), and video is also configured through the Visitor's SDK, which controls whether it is two way or one way. Audio calls are not available on premise, and viewer tokens for on-premise accounts currently have video calls disabled as well.

```tsx
import { AudioProvider, CallRingingHandler, VideoFrame, VideoProvider, ViewerProvider } from "@upscopeio/viewer-sdk";

function CobrowsingViewer({ endpoint, region, shortId, token }) {
  return (
    <ViewerProvider endpoint={endpoint} idleDisconnectSeconds={600} region={region} shortId={shortId} token={token}>
      <AudioProvider>
        <VideoProvider i18n={{ requestVideo: "Request video" }}>
          <CallRingingHandler onAudioRinging={promptForCall} onVideoRinging={promptForCall} />
          <VideoFrame />
          <Screen />
        </VideoProvider>
      </AudioProvider>
    </ViewerProvider>
  );
}
```

##### `useAudio()`

Returns the audio call state and actions. Must be used inside an `AudioProvider`.

| Field | Type | Description |
| --- | --- | --- |
| `audioState` | `"off" \| "active"` | Whether an audio call is active on the Session. |
| `audioRinging` | `boolean` | Whether an incoming audio call is ringing, waiting for this viewer to accept. |
| `acceptAudioCall` | `() => void` | Accepts the incoming audio call and starts requesting microphone access. |
| `toggleAudio` | `((on: boolean) => void) \| undefined` | Starts (`true`) or ends (`false`) the audio call. `undefined` when this browser or the Visitor's device does not support audio calls. |
| `muted` | `boolean` | Whether this viewer's microphone is muted. |
| `setMuted` | `(mute: boolean) => void` | Mutes or unmutes this viewer's microphone. |
| `inputDevices` | `object[]` | The available microphones, as browser [`MediaDeviceInfo`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo) objects. Populated once the viewer has granted microphone access. |
| `outputDevices` | `object[]` | The available speakers, as browser `MediaDeviceInfo` objects. |
| `activeInputDeviceId` | `string \| undefined` | The id of the microphone in use. |
| `activeOutputDeviceId` | `string \| undefined` | The id of the speaker in use. |
| `setInputDevice` | `(deviceId: string) => void` | Switches to another microphone. |
| `setOutputDevice` | `((deviceId: string) => void) \| undefined` | Switches to another speaker. `undefined` in browsers that do not support switching audio output. |
| `viewerAudioAvailable` | `boolean` | Whether this browser supports audio calls. |
| `visitorAudioAvailable` | `boolean` | Whether the Visitor's device supports audio calls. |
| `viewerAudioError` | `"not_authorized" \| "no_input" \| "no_output" \| undefined` | A problem with this viewer's audio: microphone access was denied, or no microphone or speaker was found. |
| `visitorAudioError` | `"not_connected" \| "not_authorized" \| "no_input" \| "no_output" \| undefined` | A problem with the Visitor's audio. |
| `visitorAudioStatus` | `"off" \| "ringing" \| "accepted" \| "authorizing" \| "active"` | Where the Visitor is in the call flow: not in the call, being rung, accepted, granting microphone access, or connected. |

##### `VideoProvider` Props

| Prop | Type | Description |
| --- | --- | --- |
| `i18n` | `{ requestVideo: string }` | The labels used by the video call widget. `requestVideo` is the label of the button that asks the Visitor to turn their camera on. |

##### `useVideo()`

Returns the video call state and actions. Must be used inside a `VideoProvider`.

| Field | Type | Description |
| --- | --- | --- |
| `videoViewerState` | `"off" \| "ringing" \| "authorizing:{connection_id}" \| "active:{connection_id}"` | The viewers' side of the video call: off, ringing, or which viewer connection is granting camera access or streaming. |
| `videoVisitorState` | `"off" \| "ringing" \| "authorizing:{connection_id}" \| "active:{connection_id}"` | The Visitor's side of the video call. |
| `videoRinging` | `boolean` | Whether an incoming video call is ringing, waiting for this viewer to accept. |
| `ownVideoOn` | `boolean` | Whether this viewer's own camera is on (granting access or streaming). |
| `beginVideo` | `() => void` | Starts the video call in the direction allowed by the Visitor's SDK configuration (two way, visitor to viewer, or viewer to visitor). |
| `endVideo` | `() => void` | Ends the video call on both sides. |
| `toggleOwnVideo` | `() => void` | Turns this viewer's own camera on or off. |
| `videoRequestToVisitor` | `() => void` | Asks the Visitor to turn their camera on. |
| `viewerVideoError` | `"not_authorized" \| "no_input" \| undefined` | A problem with this viewer's video: camera access was denied or no camera was found. |
| `visitorVideoError` | `"not_authorized" \| "no_input" \| undefined` | A problem with the Visitor's video. |

The remaining fields returned by the hook (`sendVideoCallFrame`, `setVideoMeter`, `reportViewerVideoUnauthorized`, `i18n`) are plumbing used by `VideoFrame`; you will not normally call them yourself.

##### `VideoFrame`

Renders the floating video call widget: this viewer's camera preview, the Visitor's video, and the buttons to toggle cameras. It attaches its UI directly to the page (it renders nothing in your React tree), so simply mount it anywhere inside the `VideoProvider`.

| Prop | Type | Description |
| --- | --- | --- |
| `className` | `string`, optional | An extra class for the widget container. |
| `closeButtons` | `boolean`, optional | Whether the video tiles show close buttons. Defaults to `true`. |
| `draggable` | `boolean`, optional | Whether the widget can be dragged around the page. Defaults to `true`. |
| `pipWindow` | `Window`, optional | A [document Picture-in-Picture](https://developer.mozilla.org/en-US/docs/Web/API/Document_Picture-in-Picture_API) window to render the widget into, instead of the page. |

##### `CallRingingHandler`

Coordinates incoming call prompts and plays the ringtone. When a call starts ringing, the matching callback is called with an `accept` function: show your own prompt, and call `accept(true)` or `accept(false)` with the viewer's answer. Return a cleanup function — it is called when the ringing stops (for example, the Visitor cancelled the call), so you can dismiss the prompt.

```tsx
import { CallRingingHandler, OnRinging } from "@upscopeio/viewer-sdk";

const promptForCall: OnRinging = (accept) => {
  const dismissPrompt = showIncomingCallPrompt({
    onAccept: () => accept(true),
    onRefuse: () => accept(false),
  });

  return dismissPrompt;
};
```

| Prop | Type | Description |
| --- | --- | --- |
| `onAudioRinging` | `(accept: (accepted: boolean) => void) => () => void`, optional | Called when an incoming audio call starts ringing. |
| `onVideoRinging` | `(accept: (accepted: boolean) => void) => () => void`, optional | Called when an incoming video call starts ringing. |
If audio and video ring at the same time, only `onAudioRinging` fires, and accepting or refusing it also accepts or refuses the video call.

## On Premise

Source: https://cobrowsingapi.com/docs/on-premise

### Deploying Co-Browsing API On-Premise

The easiest way to deploy Co-Browsing API is by adopting our cloud solution. It is secure, fast, and straightforward to implement, allowing us to quickly address any issues that arise.

However, we understand that some companies have stringent data security requirements, making our cloud solution unsuitable for them. To accommodate these needs, we have implemented a hybrid solution.

### Quick Start

Source: https://cobrowsingapi.com/docs/on-premise/quick-start

#### Running Co-Browsing API On-Premise

To run Co-Browsing API on your infrastructure, you'll need to either run a Docker image that contains everything or host the components separately yourself.

If you have fewer than 5,000 Visitors online at any given time and no more than 100 concurrent Sessions, a single server will likely be enough. If you have more, you'll likely need to add more servers and scale Co-Browsing API horizontally by having a separate Redis cluster.

**Docker Image:**

Pull [our image](https://hub.docker.com/r/upscope/onpremise) from Docker Hub and run it with the environment variables listed below.

```shell
docker run -d \
  -e BASE_ENDPOINT=https://cobrowsing.acmetech.com/ \
  -e LICENSE_KEY=https://api.upscope.io/v1.3/.... \
  -e SECRET_KEY=myrandomsecretkey... \
  -p 5002:5002 \
  upscope/onpremise
```

See the [Configuration](#configuration) section for all available environment variables.

**Host MongoDB Connection Issues:**
MongoDB cannot be listening only to localhost, because the container never uses the loopback network for connection. Set the `net.bindIp` to listen on `0.0.0.0` in `/etc/mongod.conf`, or—if it's a security issue—on the Docker network. This is similar for Redis, which should listen to `*:6379`.

**Docker Script:**

We've created a simple startup script to easily start and configure Co-Browsing API to use with Docker.

1. Download the [startup script (.sh file)](https://raw.githubusercontent.com/upscopeio/onprem-scripts/master/start-on-premise.sh).
2. Edit its variables according to the data from your Co-Browsing API dashboard `Settings` » `On Premise`.
3. Make it executable: `chmod +x start-on-premise.sh`.
4. Run it: `./start-on-premise.sh`.

**Host MongoDB Connection Issues:**
MongoDB cannot be listening only to localhost, because the container never uses the loopback network for connection. Set the `net.bindIp` to listen on `0.0.0.0` in `/etc/mongod.conf`, or—if it's a security issue—on the Docker network. This is similar for Redis, which should listen to `*:6379`.

**Binaries:**

If you don't want to use Docker, you'll find Co-Browsing API binaries on the link provided in your Co-Browsing API dashboard `Settings` » `On Premise`.

This link never changes, so you can `curl` the binaries as part of your build process and automatically keep Co-Browsing API up to date. Any breaking change will have a new link, so you don't need to worry about that.

You can start the server by running:

```shell
BASE_ENDPOINT=http://localhost:5002 PORT=5002 \
LICENSE_KEY=https://api.upscope.io/v1.3/.... \
SECRET_KEY=myrandomsecretkey... ./upscope-data-linux
```

#### Installing Co-Browsing API on Your Website

After you run Co-Browsing API, the output will give you instructions for your [JavaScript SDK](https://cobrowsingapi.com/docs/sdk/web/installation.md) installation code. It looks like this (notice `{BASE_ENDPOINT}`):

```html
<script>
  (function(w, u, d){if(typeof u!=="function"){var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};
  w.Upscope=i;var l = function(){var s=d.createElement('script');s.type='text/javascript';s.async=true;
  s.src='{BASE_ENDPOINT}/upscope.js';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);};l();}}
  )(window, window.Upscope, document);
  Upscope('init');
  Upscope('getWatchLink', console.log);
</script>
```

You can add that code to pages like you would with our cloud solution.

#### Installing Co-Browsing API in Your Mobile Apps

The [iOS](https://cobrowsingapi.com/docs/sdk/ios/installation.md), [Android](https://cobrowsingapi.com/docs/sdk/android/installation.md), [Flutter](https://cobrowsingapi.com/docs/sdk/flutter/installation.md), and [React Native](https://cobrowsingapi.com/docs/sdk/react-native/installation.md) SDKs connect to your on-premise instance when you set the `onPremiseBaseEndpoint` configuration option to your `BASE_ENDPOINT`. The SDK then fetches its configuration from `{BASE_ENDPOINT}/sdk-config.json` and connects to `{BASE_ENDPOINT}/session` instead of our cloud servers, so the `region` option is ignored.

**iOS:**

```swift
let config = UpscopeConfiguration(
    apiKey: "YOUR_API_KEY",
    onPremiseBaseEndpoint: "https://cobrowsing.acmetech.com"
)

try Upscope.shared.initialize(with: config)
```

**Android:**

```kotlin
val config = UpscopeConfiguration.Builder("YOUR_API_KEY")
    .onPremiseBaseEndpoint("https://cobrowsing.acmetech.com")
    .build()

Upscope.initialize(applicationContext, config)
```

**Flutter:**

```dart
final config = UpscopeConfiguration(
  apiKey: 'YOUR_API_KEY',
  onPremiseBaseEndpoint: 'https://cobrowsing.acmetech.com',
);

await Upscope.instance.initialize(config);
```

**React Native:**

```typescript
import Upscope from "@upscopeio/react-native-sdk";

Upscope.initialize({
  apiKey: "YOUR_API_KEY",
  onPremiseBaseEndpoint: "https://cobrowsing.acmetech.com",
});
```

Everything else — installation, configuration options, and SDK functions — works exactly like the cloud version.

#### Your License Key

To run Co-Browsing API, you'll need to retrieve your license key. The license key can be downloaded to your server or read automatically from our server every time the server starts.

You'll need to enter the license key into the `LICENSE_KEY` environment variable. The `LICENSE_KEY` environment variable can be one of:

- The license key content (note it's multi-line)
- A file path to the license key content
- The URL to the license key, the preferred method

If you don't have to restrict the instance's interactions with the internet, entering the URL provided on the [dashboard](https://app.upscope.io/settings/teams/_/on_prem) as the `LICENSE_KEY` environment variable is preferable as you won't need to update it when it expires.

**Good to Know:**
Your JavaScript SDK configuration is also embedded in your license key. This means you can still configure Co-Browsing API on your dashboard and then restart the server to apply the changes.

#### Configuration

The following is configurable through environment variables:

| Environment Variable | Description | Default |
| --- | --- | --- |
| `BASE_ENDPOINT` | The base URL where this component will be mounted. For example, `https://cobrowsing.acmetech.com/`. It can be in a subdirectory. | (nil, **required**) |
| `LICENSE_KEY` | Your unique Co-Browsing API license key (or a link to it). | (nil, **required**) |
| `SECRET_KEY` | A unique secret key used to sign internal JWTs. **It's very important this key is kept safe and it's at least 32 characters long.** | (nil, **required**) |
| `AUTH_ENDPOINT` | URL watch links will be redirected to for authentication. | `https://app.upscope.io/onprem_redirect/TEAM_IDENTIFIER` |
| `HOMEPAGE` | URL unrecognized requests will be redirected to. | `https://upscope.com/` |
| `LOOKUP_CODE_LENGTH` | The number of digits in visitor lookup codes. | `4` |
| `MONGO_URI` | URI to a single MongoDB instance, or MongoDB cluster | If omitted, `mongodb://localhost:27017/upscope`. (In Docker this starts MongoDB within the image). If set to an empty string, it will disable MongoDB. |
| `PORT` | The port the server will listen on. | `5002` |
| `REDIS_URI` | URI to Redis or a Redis cluster. | `redis://localhost:6379`. (In Docker this starts Redis within the image). |
| `REST_KEY` | The authentication API key for your on-premise REST API. | (nil) |
| `SSL_REDIRECT` | When set to on, the server will automatically redirect all requests to https. | `off` |
| `TOKEN_EXPIRY_SECONDS` | The number of seconds watch links and viewer tokens generated through the REST API stay valid. Must be a whole number. | `60` |

If you use `MONGO_URI=mongodb://localhost/upscope` with the Docker image, a MongoDB server will be installed inside the container to serve the application.

If you use `redis://localhost/` with the Docker image, a Redis server will be installed inside the container to serve the application.

#### Session Video Recording

Your on-premise instance can record Sessions and render them to MP4, with the video files never leaving your infrastructure. For recording to work you need:

1. The session recording feature on your plan. It's embedded in your license key, so if you just added it, re-download the license key (or use the URL form of `LICENSE_KEY`) and restart the server.
2. MongoDB (`MONGO_URI` must not be disabled): Session events used for the replay are stored there.
3. For Sessions that include browser co-browsing, a separate Chromium container the server can reach over WebSocket. We test with the [browserless Chromium image](https://github.com/browserless/browserless):

```shell
docker run -d -e TOKEN=mybrowserlesstoken -p 3000:3000 ghcr.io/browserless/chromium
```

Then point your Co-Browsing API instance at it with `BROWSERLESS_ENDPOINT=ws://yourdockerhost:3000?token=mybrowserlesstoken`. Sessions made only of screen sharing frames are rendered with ffmpeg directly and don't need the Chromium container (if you run the binaries instead of Docker, make sure `ffmpeg` is available on the PATH).

Nothing is rendered until you ask for it. Once a Session has ended, `POST` its `session_id` to `{BASE_ENDPOINT}/api/recordings/{visitor_short_id}`, authenticated with a short-lived HS256 token signed with your `SECRET_KEY`. The first call starts the render, later calls report progress, and the video URL comes back once it's ready.
See [Requesting a Session Recording](https://cobrowsingapi.com/docs/on-premise.md#requesting-a-session-recording) for the full request and its responses.

A Session can't be rendered if no events were stored for it, if it needs a browser and `BROWSERLESS_ENDPOINT` is unset, or if MongoDB is disabled.

Recordings can be stored on a local volume (the default) or on S3:

| Environment Variable | Description | Default |
| --- | --- | --- |
| `RECORDINGS_STORAGE` | `volume` stores recordings on disk under `/recordings` (mount a volume there to persist them) and serves them from `{BASE_ENDPOINT}/recordings`; `s3` uploads them to S3 instead. | `volume` |
| `RECORDINGS_S3_BUCKET` | The S3 bucket recordings are uploaded to when using `s3` storage. Falls back to `S3_BUCKET`. | (nil) |
| `ACCESS_KEY_ID`, `SECRET_ACCESS_KEY`, `AWS_REGION` | The AWS credentials used for `s3` storage. | (nil) |
| `RECORDINGS_BASE_URL` | The public base URL recording links point to. Set it when using `s3` storage (your bucket or CDN URL). | `{BASE_ENDPOINT}/recordings` |
| `BROWSERLESS_ENDPOINT` | WebSocket endpoint of the Chromium container used to replay browser Sessions. When unset, only screen sharing Sessions can be recorded. | (nil) |
| `RENDER_RECORD_ENDPOINT` | The URL the Chromium container loads to render the replay. Override it when your `BASE_ENDPOINT` is not reachable from that container. | `{BASE_ENDPOINT}/record` |

Recording file URLs are not guessable (they contain a UUID), matching the access model of our cloud storage.
You can skip recording an individual Session with the `session_properties.record` parameter on the [watch link and viewer token endpoints](https://cobrowsingapi.com/docs/on-premise.md#generating-a-link-to-start-screen-sharing-with-a-visitor).

#### API Endpoint

Check the [on-premise REST API docs](https://cobrowsingapi.com/docs/on-premise.md) to know how to make requests to the on-premise instance.

#### What Doesn't Work On-Premise?

Although we've made our on-premise version as similar as possible to the original, there are a few missing functionalities. You'll need to use Co-Browsing API cloud if you are interested in:

- Presentation sharing
- IP geolocation features
- Audio/video communication

##### Usage Without MongoDB

We are aware some of our customers cannot use MongoDB due to compliance reasons.

Co-Browsing API works fine without MongoDB, but the Search feature will be limited. You'll still be able to search by:

- **Lookup code**
- **Email**
- **Unique ID**
- **Integration IDs**

### REST API (on premise)

Source: https://cobrowsingapi.com/docs/on-premise/rest-api-on-premise

#### On-Premise REST API Overview

The on-premise REST API functions similarly to the cloud version, with some differences detailed below.

#### Enabling the API

By default, the API is disabled. It will only be enabled when you add a `REST_KEY` environment variable.

#### Authentication

To authenticate your requests to the REST API, add an `Authorization` header set to the value you provided in the `REST_KEY` environment variable.

#### The Visitor Object

The **Visitor** object may differ slightly in the on-premise version, depending on your configuration. Most attributes will be present, and all Visitors will have the same properties.

#### Session IDs

The watch link and viewer token endpoints return a `session_id` like `onprem_s_3f2c8a1e-9d47-4b02-8f61-05c7ae13d9b4`. Your instance generates it, so it needs no connection to our servers. Keep it: it's what you pass back to [request the Session's recording](#requesting-a-session-recording).

#### Endpoints

On-premise, the REST API is mounted on the `/api` path of your base endpoint. For example, if your `BASE_ENDPOINT` is `https://support.acmetech.com/cobrowsing`, one of the endpoints will be `https://support.acmetech.com/cobrowsing/api/visitors/list/latest`.

##### Listing the Latest Visitors

Use this endpoint to list the latest online Visitors. This only works with MongoDB.

```http
GET /v1.4/visitors/list/latest
```

| Parameter     | Type               | Description                              |
| ------------- | ------------------ | ---------------------------------------- |
| `max_results` | `number`, optional | The max number of results to return.     |

**Response**

```javascript
// HTTP 200
{
  "status": "ok",
  "visitors": [
    {
      "short_id": "1234",
      // ...
    }
  ]
}
```

| Field      | Type        | Description                          |
| ---------- | ----------- | ------------------------------------ |
| `visitors` | `Visitor[]` | List of Visitors matching the query. |

##### Searching for a Visitor

Use this endpoint to search for Visitors.

```http
GET /v1.4/visitors/list/search
```

| Parameter     | Type               | Description                                                                            |
| ------------- | ------------------ | -------------------------------------------------------------------------------------- |
| `q`           | `string`, optional | The search query in this format. If left empty, the latest Visitors will be returned. |
| `max_results` | `number`, optional | The max number of results to return.                                                   |

**Response**

```javascript
// HTTP 200
{
  "status": "ok",
  "visitors": [
    {
      "short_id": "1234",
      // ...
    }
  ]
}
```

| Field      | Type        | Description                          |
| ---------- | ----------- | ------------------------------------ |
| `visitors` | `Visitor[]` | List of Visitors matching the query. |

##### Retrieving a Visitor

Use this endpoint to retrieve a specific Visitor.

```http
GET /v1.4/visitors/{visitor_id}
```

| Parameter        | Type                | Description                                                            |
| ---------------- | ------------------- | ---------------------------------------------------------------------- |
| `{visitor_id}`   | `string`            | The short id of the Visitor.                                           |
| `includeHistory` | `boolean`, optional | Set to `true` to include the Visitor's pageview history (MongoDB only). |

**Response**

```javascript
// HTTP 200
{
  "status": "ok",
  "visitor": {
    "short_id": "1234",
    "history": [
      // Pageviews, if includeHistory was set to true
    ],
    // ...
  }
}
```

| Field             | Type                    | Description                                                       |
| ----------------- | ----------------------- | ----------------------------------------------------------------- |
| `visitor`         | `Visitor`               | The Visitor.                                                      |
| `visitor.history` | `Pageview[]`, optional  | The Visitor's latest pageviews. Only present with `includeHistory`. |

**Errors**

| Code  | Description                 |
| ----- | --------------------------- |
| `404` | The Visitor does not exist. |

##### Generating a Link to Start Screen Sharing with a Visitor

To start a Session with one of your Visitors, first obtain an authorization token from our server, then make a request to your on-premise REST API.

```javascript
// POST /v1.4/visitors/{visitor_id}/watch_url
{
  "licenseToken": "token1234",
  "branding": {},
  "permissions": {},
  "viewer": {
    "id": "123",
    "name": "Joe Smith"
  }
}
```

| Parameter                                    | Type                       | Description                                                                                                                                                                           |
| -------------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{visitor_id}`                               | `string`                   | The short id of the Visitor.                                                                                                                                                          |
| `licenseToken`                               | `string`                   | The token obtained from [here](https://cobrowsingapi.com/docs/rest-api/api-endpoints.md#authorizing-an-on-premise-session). Reach out to our team if this is incompatible with your flow for alternative options. |
| `viewer.id`                                  | `string`                   | The ID of the person viewing the screen (required).                                                                                                                                   |
| `viewer.name`                                | `string`, optional         | The name of the person viewing the screen.                                                                                                                                            |
| `agent.id`                                   | `string`, optional         | The ID of the Agent responsible for the Session. When set, the Agent (rather than the viewer) is the identity used for licensing and usage tracking, and the `licenseToken` must have been generated for this Agent. Use it when the person viewing the screen is not one of your Agents — for example when the Visitor is viewing their own Session — so each viewer is not counted as a separate Agent. |
| `agent.name`                                 | `string`, optional         | The name of the Agent responsible for the Session.                                                                                                                                    |
| `branding.naked`                             | `"page_only"`, optional    | Set to `"page_only"` to remove everything on the page except the shared screen, including the viewer's UI. Ideal when embedding the page inside an iframe.                            |
| `branding.show_end_button`                   | `boolean`, optional        | Whether an "End session" button should be shown on the viewer page.                                                                                                                   |
| `branding.on_end_url`                        | `string \| null`, optional | If set, the viewer will be redirected to this URL after the Session ends. Defaults to `null`.                                                                                         |
| `branding.retry_url`                         | `string \| null`, optional | If set, the visitor can click a "Retry now" link that redirects to this URL if there are problems. Defaults to `null`.                                                                |
| `permissions.allow_click`                    | `boolean`, optional        | Whether the viewer should have access to remote click (defaults to `true`).                                                                                                           |
| `permissions.allow_confetti`                 | `boolean`, optional        | Whether the viewer should be able to throw confetti (defaults to `true`).                                                                                                             |
| `permissions.allow_console`                  | `boolean`, optional        | Whether the viewer should have access to remote console capabilities (defaults to `false`).                                                                                           |
| `permissions.allow_draw`                     | `boolean`, optional        | Whether the viewer should have access to drawing (defaults to `true`).                                                                                                                |
| `permissions.allow_redirect`                 | `boolean`, optional        | Whether the viewer should have access to remote redirect & reload features (defaults to `true`). Called `allow_agent_redirect` before v1.4.                                           |
| `permissions.allow_request_control`          | `boolean`, optional        | Whether the viewer should be able to request remote control when the Visitor's SDK requires a control request (defaults to `true`).                                                   |
| `permissions.allow_request_full_tab`         | `boolean`, optional        | Whether the viewer should be able to request full browser tab sharing (defaults to `false`).                                                                                          |
| `permissions.allow_request_mode`             | `boolean`, optional        | Whether the viewer should be able to request a different sharing mode, such as full screen sharing (defaults to `true`).                                                              |
| `permissions.allow_request_visitor_screen`   | `boolean`, optional        | Whether the viewer should be able to ask the Visitor to share their full screen (defaults to `true`).                                                                                 |
| `permissions.allow_scroll`                   | `boolean`, optional        | Whether the viewer should have access to remote scroll (defaults to `true`).                                                                                                          |
| `permissions.allow_show_viewer_screen`       | `boolean`, optional        | Whether the viewer should be able to share their screen with the Visitor (defaults to `true`). Called `allow_show_agent_screen` before v1.4.                                          |
| `permissions.allow_type`                     | `boolean`, optional        | Whether the viewer should have access to remote type (defaults to `true`).                                                                                                            |
| `permissions.allow_video`                    | `boolean`, optional        | Whether the viewer should have access to video calls (defaults to `true`). Audio calls are not available on premise.                                                                  |
| `permissions.hide_private_details`           | `boolean`, optional        | Whether the Visitor's private information (such as IP address) should be hidden from the viewer (defaults to `false`).                                                                |
| `permissions.keeps_session_alive`            | `boolean`, optional        | Whether this viewer keeps the Session alive. Set to `false` for silent observers, so the Session ends when the last keep-alive viewer leaves (defaults to `true`).                    |
| `session_properties.controller`              | `Array<"viewer" \| "visitor">`, optional | Which sides of the Session can start video calls and request a different sharing mode. Defaults to `["viewer"]`.                                                        |
| `session_properties.record`                  | `boolean`, optional        | Whether this Session should be recorded. Defaults to `true` whenever session recording is set up on your server; set it to `false` to skip recording this Session.                   |

**Upgrading from v1.3?:**
The `agent` object was replaced by the `viewer` object, and `viewer.id` is now required. In v1.4, `agent` is instead an optional, separate object identifying the Agent the Session is licensed to (see above). The `permissions.allow_agent_redirect` and `permissions.allow_show_agent_screen` options were renamed to `permissions.allow_redirect` and `permissions.allow_show_viewer_screen`. Requests to the `/v1.3/` endpoints keep working with the old names.

**Response**

```javascript
// HTTP 200
{
    "status": "ok",
    "watch_url": "https://support.acmetech.com/cobrowsing/screen?id=jfoiajsdoif&token=osdjfoijo",
    "session_id": "onprem_s_3f2c8a1e-9d47-4b02-8f61-05c7ae13d9b4"
}
```

| Field        | Type     | Description                                                                                                                             |
| ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `watch_url`  | `string` | The Secure watch URL. It stays valid for 60 seconds (configurable on your instance with `TOKEN_EXPIRY_SECONDS`), so open it right away. |
| `session_id` | `string` | The id of the Session this link starts. Use it to [request the Session's recording](#requesting-a-session-recording).                   |

**Errors**

| Code  | Description                                                |
| ----- | ---------------------------------------------------------- |
| `400` | A required parameter is missing or invalid.                |
| `402` | This viewer would put you over your subscription limits.   |
| `404` | The Visitor does not exist.                                |
| `409` | The Visitor's browser is not supported.                    |

##### Generating a Viewer Token to Embed Co-Browsing

Use this endpoint to generate a viewer token instead of a watch link. The token lets you embed the co-browsing screen directly in your own application with the [viewer SDK](https://cobrowsingapi.com/docs/rest-api/viewer-sdk.md), instead of opening the hosted watch page. The Visitor's browser is asked to prepare for the Session right away, so only request a token when the viewer is about to connect.

```javascript
// POST /v1.4/visitors/{visitor_id}/viewer_token
{
  "licenseToken": "token1234",
  "permissions": {},
  "viewer": {
    "id": "123",
    "name": "Joe Smith"
  }
}
```

| Parameter      | Type     | Description                                                                                                                                                                           |
| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{visitor_id}` | `string` | The short id of the Visitor.                                                                                                                                                          |
| `licenseToken` | `string` | The token obtained from [here](https://cobrowsingapi.com/docs/rest-api/api-endpoints.md#authorizing-an-on-premise-session). Reach out to our team if this is incompatible with your flow for alternative options. |

The endpoint also accepts the same `viewer`, `agent`, `permissions` and `session_properties` parameters as the watch link endpoint above.

**Response**

```javascript
// HTTP 200
{
    "status": "ok",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....",
    "endpoint": "https://support.acmetech.com/cobrowsing",
    "region": "onpremise",
    "short_id": "1234",
    "session_id": "onprem_s_3f2c8a1e-9d47-4b02-8f61-05c7ae13d9b4",
    "expiration": "2026-01-11T18:01:49.700Z"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `token` | `string` | The token to pass to the viewer SDK. |
| `endpoint` | `string` | The server the viewer should connect to (your on-premise instance). |
| `region` | `string` | Always `onpremise`. |
| `short_id` | `string` | The short id of the Visitor. |
| `session_id` | `string` | The id of the Session this token starts. Use it to [request the Session's recording](#requesting-a-session-recording). |
| `expiration` | `Datetime` (ISO 8601) | Time at which the token is no longer valid. It's 60 seconds after issue unless your instance sets `TOKEN_EXPIRY_SECONDS`. Use it right away. |

**Errors**

| Code  | Description                                                |
| ----- | ---------------------------------------------------------- |
| `400` | A required parameter is missing or invalid.                |
| `402` | This viewer would put you over your subscription limits.   |
| `404` | The Visitor does not exist.                                |
| `409` | The Visitor's browser is not supported.                    |

##### Requesting a Session Recording

Renders an ended Session to MP4 on your own instance. See [Session Video Recording](https://cobrowsingapi.com/docs/on-premise/quick-start.md#session-video-recording) for the storage and Chromium setup it needs.

**This Endpoint Authenticates Differently:**
This one does not use `REST_KEY`. Send an `Authorization: Bearer <jwt>` header holding a short-lived HS256 JWT signed with your `SECRET_KEY`, with the payload `{"scope": "cobrowsing-data-call", "apiKeys": "*"}`.

```javascript
// POST /v1.4/recordings/{visitor_id}
{
  "sessionId": "onprem_s_3f2c8a1e-9d47-4b02-8f61-05c7ae13d9b4"
}
```

| Parameter      | Type     | Description                                                                             |
| -------------- | -------- | ----------------------------------------------------------------------------------------- |
| `{visitor_id}` | `string` | The short id of the Visitor.                                                            |
| `sessionId`    | `string` | The `session_id` returned when you generated the watch link or viewer token.            |

Rendering is asynchronous. The first request starts it, and further requests report progress until the video is ready. Poll about once a minute rather than tightly, as rendering a long Session takes a while.

**Response**

```javascript
// HTTP 200, still rendering
{
    "status": "ok",
    "video_status": "recording"
}

// HTTP 200, finished
{
    "status": "ok",
    "video_status": "ready",
    "video_url": "https://support.acmetech.com/cobrowsing/recordings/APIKEY/1234/6f1b....mp4"
}
```

| Field          | Type     | Description                                                                                                      |
| -------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `video_status` | `string` | `recording` while the render is in progress, `ready` once the video is available.                                |
| `video_url`    | `string` | Present when `video_status` is `ready`. Points at your own storage. Held for 6 hours after the render completes. |

**Errors**

| Code  | Description                                                                                             |
| ----- | --------------------------------------------------------------------------------------------------------- |
| `404` | No events were recorded for this Session, so there is nothing to render.                                 |
| `501` | MongoDB is disabled on your instance, or the Session needs a browser and `BROWSERLESS_ENDPOINT` is unset. |

##### Preparing a Session

Use this endpoint to ask the Visitor's browser to prepare for a Session ahead of time, so the Session starts faster. This is done automatically when you generate a watch link.

```http
POST /v1.4/visitors/{visitor_id}/session/prepare
```

| Parameter      | Type     | Description                  |
| -------------- | -------- | ---------------------------- |
| `{visitor_id}` | `string` | The short id of the Visitor. |

**Response**

```javascript
// HTTP 200
{
  "status": "ok"
}
```

**Errors**

| Code  | Description                 |
| ----- | --------------------------- |
| `404` | The Visitor does not exist. |

##### Stopping an Active Session

Use this endpoint to stop the Visitor's active Session.

```http
DELETE /v1.4/visitors/{visitor_id}/session
```

| Parameter      | Type     | Description                  |
| -------------- | -------- | ---------------------------- |
| `{visitor_id}` | `string` | The short id of the Visitor. |

**Response**

```javascript
// HTTP 200
{
  "status": "ok",
  "visitor": {
    "short_id": "1234",
    // ...
  }
}
```

| Field     | Type      | Description  |
| --------- | --------- | ------------ |
| `visitor` | `Visitor` | The Visitor. |

**Errors**

| Code  | Description                 |
| ----- | --------------------------- |
| `404` | The Visitor does not exist. |

##### Deleting a Visitor

Use this endpoint to delete a Visitor entirely, including their pageview history and Session recordings. You can only delete Visitors who are not currently online or in a Session.

```http
DELETE /v1.4/visitors/{visitor_id}
```

| Parameter      | Type     | Description                  |
| -------------- | -------- | ---------------------------- |
| `{visitor_id}` | `string` | The short id of the Visitor. |

**Response**

```javascript
// HTTP 200
{
  "status": "ok"
}
```

**Errors**

| Code  | Description                                 |
| ----- | ------------------------------------------- |
| `404` | The Visitor does not exist.                 |
| `409` | The Visitor is online or in a Session.      |

##### Deleting All Visitors

Use this endpoint to delete all Visitors, along with their pageview history and Session recordings.

```http
DELETE /v1.4/visitors
```

**Response**

```javascript
// HTTP 200
{
  "status": "ok"
}
```

## Presentation Sharing

Source: https://cobrowsingapi.com/docs/presentation-sharing

Presentation Sharing allows agents to share internal presentations or dashboards with external viewers. This mode is designed for platforms where you need to generate a public link that customers can view without authentication.

### How It Works

1. **Add the SDK** - Install the Co-Browsing API SDK on your presentation platform
2. **Get a Public Link** - Use the `getPublicLink` function to generate a shareable URL
3. **Share with Viewers** - Send the link to anyone who needs to see your presentation

### Use Cases

- Sales presentations from internal dashboards
- Training content from internal systems
- Demos from platforms like Salesforce

### Setup

This mode is typically set up by administrators. No agent accounts need to be created - the presenting user's information is passed directly to the SDK.

### Getting Started

1. [SDK Integration](https://cobrowsingapi.com/docs/presentation-sharing/sdk-integration.md) - Add the SDK to your platform
2. [Getting Public Links](https://cobrowsingapi.com/docs/presentation-sharing/getting-public-link.md) - Generate shareable URLs
3. [Salesforce Example](https://cobrowsingapi.com/docs/presentation-sharing/salesforce-example.md) - Complete Salesforce Visualforce implementation

### SDK Integration

Source: https://cobrowsingapi.com/docs/presentation-sharing/sdk-integration

Add the Co-Browsing API SDK to any page or application where you want to enable presentation sharing.

#### Installation

Get the installation code from your [Co-Browsing API dashboard](https://cobrowsingapi.com/install) and add it to your page.

#### What Gets Shared

The SDK shares the entire page. If you only want to share a specific section of the page, place that content in an iframe and initialize the SDK within that iframe instead.

#### Session Metadata

You can associate additional data with the session, such as customer information:

```javascript
Upscope("updateConnection", {
  metadata: {
    customerId: "12345",
    accountName: "Acme Corp"
  }
});
```

This metadata will be stored with the session.

### Getting Public Links

Source: https://cobrowsingapi.com/docs/presentation-sharing/getting-public-link

#### The getPublicLink Function

Use the `getPublicLink` SDK function to generate a shareable URL that external viewers can access without authentication.

##### Basic Usage

```javascript
Upscope("getPublicLink", {
  agentId: "agent-unique-id",
  agentName: "Agent Name"
}, function(link) {
  console.log("Share this link: " + link);
});
```

##### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `agentId` | string | Yes | A unique identifier for the agent (e.g., user ID from your system) |
| `agentName` | string | Yes | Display name shown to viewers |

##### Callback Response

The callback receives a URL string that viewers can open directly in their browser.

Example link format: `https://company.cobrowsingapi.com/123456`

**Custom Domain:**
If you want to change your company domain, please contact support.

#### Displaying the Link

Here's an example of how you might display the link in your UI:

```javascript
function displayLink() {
  Upscope("getPublicLink", {
    agentId: currentUser.id,
    agentName: currentUser.name
  }, function(link) {
    document.getElementById("public-link").textContent = link;
    document.getElementById("link-container").style.display = "block";
  });
}
```

### Salesforce Visualforce Example

Source: https://cobrowsingapi.com/docs/presentation-sharing/salesforce-example

#### Complete Salesforce Implementation

This example shows how to add presentation sharing to a Salesforce Visualforce page.

##### Visualforce Page

```html
<apex:page>
  <script>
    // Co-Browsing API SDK compatibility flag for Lightning/Aura
    window.__upscopeFlags__ = ["aura_compatibility"];

    // ... rest of code found on https://cobrowsingapi.com/install

    // Pass additional metadata about the current context
    Upscope("updateConnection", {
      metadata: {
        recordId: "{!record.Id}",
        recordType: "{!record.RecordType.Name}"
      }
    });

    // Agent identification from Salesforce user
    var agentId   = '{!$User.Id}';
    var agentName = '{!JSENCODE($User.Name)}';

    function showLink() {
      Upscope("getPublicLink", {
        agentId: agentId,
        agentName: agentName
      }, function(link) {
        alert("Share this link with the customer: " + link);
      });
    }
  </script>

  <button onclick="showLink()">Get Public Link</button>

  <!-- Your presentation content here -->
  <apex:pageBlock title="Presentation">
    <!-- ... -->
  </apex:pageBlock>
</apex:page>
```

##### Key Points

1. **Aura Compatibility Flag**: The `window.__upscopeFlags__ = ["aura_compatibility"]` line ensures the SDK works correctly within Salesforce's Lightning framework.

2. **User Context**: We use Visualforce merge fields (`{!$User.Id}`, `{!JSENCODE($User.Name)}`) to automatically get the current Salesforce user's information.

3. **JSENCODE**: Always use `{!JSENCODE()}` for string values to prevent issues with quotes.

4. **Metadata**: Pass relevant Salesforce record information via `updateConnection` to provide context in reports.
