> For the complete documentation index, see [llms.txt](https://docs.sudoplatform.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sudoplatform.com/guides/privacy-interaction/manage-virtual-presences.md).

# Manage Virtual Presences

### Connecting a Virtual Presence <a href="#connecting-a-virtual-presence" id="connecting-a-virtual-presence"></a>

In order for the Privacy Interaction Service to scan a user's virtual presence, the user must grant the service access via OAuth 2.0. Your application obtains an OAuth credential from the provider and passes it to the SDK, which transmits it securely to the Privacy Interaction Service backend. The service then uses this credential to periodically access the connected account and discover data holders.

The SDK accepts credentials in one of two forms:

* **Authorization code** — A short-lived code obtained when the user completes the OAuth consent flow in your application. The Privacy Interaction Service exchanges this code server-side for an access token and refresh token, which it stores securely and uses for ongoing access.
* **Refresh token** — A long-lived credential your application has already obtained from a prior OAuth flow. Providing a refresh token directly allows the service to begin scanning without needing to perform the initial code exchange. This is useful when your application manages its own OAuth flow and already holds the user's credentials.

In both cases, the service uses the credential to obtain short-lived access tokens as needed. If the credential expires or is revoked by the user, the virtual presence transitions to the `NEEDS_REAUTH` state and your application should prompt the user to re-authenticate.

The required OAuth scopes and what data the service accesses depend on the virtual presence type. See [Supported Virtual Presence Types](http://localhost:63342/markdownPreview/791380626/markdown-preview-index-d8h902kdha0me1tor8gvq41hrs.html#supported-virtual-presence-types) for details.

#### Connect with Authorization Code <a href="#connect-with-authorization-code" id="connect-with-authorization-code"></a>

Use this method when your application has completed an OAuth flow and received an authorization code from the provider. The authorization code has enough information embedded to sufficiently identify the virtual presence.

{% tabs %}
{% tab title="Typescript" %}

```typescript
try {
  const virtualPresence = await privacyInteractionClient.connectVirtualPresenceWithAuthCode(
    authCode,
  )
  // The virtual presence is now connected.
  // `virtualPresence.state` will be `CONNECTED` or `SCANNING`.
} catch (error) {
  // Handle/notify user of errors
}
```

{% endtab %}
{% endtabs %}

#### Connect with Refresh Token <a href="#connect-with-refresh-token" id="connect-with-refresh-token"></a>

Use this method when your application already holds a valid refresh token for the provider.

{% tabs %}
{% tab title="Typescript" %}

```typescript
try {
  const virtualPresence = await privacyInteractionClient.connectVirtualPresenceWithRefreshToken({
    refreshToken: 'refresh-token-value',
    providerIdentity: 'user@example.com',
    scopes: ['https://www.googleapis.com/auth/gmail.readonly'],
    expiresInEpochMs: 1700000000000, // optional
  })
  // The virtual presence is now connected.
} catch (error) {
  // Handle/notify user of errors
}
```

{% endtab %}
{% endtabs %}

**Input Parameters:**

| Parameter          | Type      | Required | Description                                                      |
| ------------------ | --------- | -------- | ---------------------------------------------------------------- |
| `refreshToken`     | string    | Yes      | The refresh token value.                                         |
| `providerIdentity` | string    | Yes      | The identity associated with the provider (e.g., email address). |
| `scopes`           | string\[] | No       | OAuth scopes associated with the token.                          |
| `expiresInEpochMs` | number    | No       | Token expiration time in milliseconds since epoch.               |

### Disconnecting a Virtual Presence <a href="#disconnecting-a-virtual-presence" id="disconnecting-a-virtual-presence"></a>

To disconnect a virtual presence and stop it from being scanned, call `disconnectVirtualPresence` with the virtual presence's unique identifier.

When a virtual presence is disconnected, its state will transition to `INACTIVE`. Any existing data holders and analysis results associated with the virtual presence will remain accessible.

{% tabs %}
{% tab title="First Tab" %}

```typescript
try {
  const virtualPresence = await privacyInteractionClient.disconnectVirtualPresence(
    virtualPresenceId,
  )
  // The virtual presence is now disconnected.
  // `virtualPresence.state` will be `INACTIVE`.
} catch (error) {
  // Handle/notify user of errors
}
```

{% endtab %}
{% endtabs %}

### Listing Virtual Presences <a href="#listing-virtual-presences" id="listing-virtual-presences"></a>

To retrieve all virtual presences for the signed-in user, use the `listVirtualPresences` method. Results are paginated.

A call to `listVirtualPresences` returns a `ListOutput` object containing a list of matching `items` and a `nextToken` to support pagination. If no results are found, the result will contain empty items. Always check the value of the returned `nextToken`—there are no more results to retrieve when `nextToken` is undefined.

{% tabs %}
{% tab title="Typescript" %}

```typescript
try {
  const listOutput = await privacyInteractionClient.listVirtualPresences({
    limit: 20,
    nextToken: undefined,
  })
  // `listOutput.items` contains the virtual presences.
  // Page through results if `listOutput.nextToken` is defined.
} catch (error) {
  // Handle/notify user of errors
}
```

{% endtab %}
{% endtabs %}

**Input Parameters:**

| Parameter   | Type   | Required | Description                                                      |
| ----------- | ------ | -------- | ---------------------------------------------------------------- |
| `limit`     | number | No       | Maximum number of items to return. Will be defaulted if omitted. |
| `nextToken` | string | No       | A pagination token from a previous call.                         |

### Virtual Presence States <a href="#virtual-presence-states" id="virtual-presence-states"></a>

A virtual presence transitions through the following states:

| State          | Description                                                                                                                                                                                                                        |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CONNECTED`    | The virtual presence is active and connected to the provider.                                                                                                                                                                      |
| `SCANNING`     | The virtual presence is currently being scanned for data holders.                                                                                                                                                                  |
| `NEEDS_REAUTH` | The virtual presence requires re-authentication with the provider. This will happen automatically when the refresh token expires, or when the user explicitly disconnects the Privacy Interaction application from their provider. |
| `ERROR`        | The virtual presence encountered an error and is in a failed state.                                                                                                                                                                |
| `INACTIVE`     | The virtual presence has been disconnected and is no longer active.                                                                                                                                                                |

When a virtual presence enters the `NEEDS_REAUTH` state, your application should prompt the user to re-authenticate with the provider and call `connectVirtualPresenceWithAuthCode` or `connectVirtualPresenceWithRefreshToken` again.

### Supported Virtual Presence Types <a href="#supported-virtual-presence-types" id="supported-virtual-presence-types"></a>

#### Gmail Inbox <a href="#gmail-inbox" id="gmail-inbox"></a>

| Property             | Value                                             |
| -------------------- | ------------------------------------------------- |
| Provider type        | `EMAIL`                                           |
| Required OAuth scope | `https://www.googleapis.com/auth/gmail.readonly`  |
| Provider identity    | The user's Gmail address (e.g., `user@gmail.com`) |

When connected with the `gmail.readonly` scope, the service scans email headers — sender addresses, subject lines, and metadata — to identify data holders (companies and services the user has interacted with). It does not read email body content.

The service periodically re-scans the inbox to discover new data holders as new emails arrive. Each scan updates the virtual presence's `lastScannedAt` timestamp.

**Example: Connect with auth code (Gmail)**

{% tabs %}
{% tab title="Typescript" %}

```typescript
// After the user completes Google's OAuth consent flow and you receive an auth code
// scoped to https://www.googleapis.com/auth/gmail.readonly
const virtualPresence = await privacyInteractionClient.connectVirtualPresenceWithAuthCode(
  authCode,
)
```

{% endtab %}
{% endtabs %}

**Example: Connect with refresh token (Gmail)**

{% tabs %}
{% tab title="Typescript" %}

```typescript
const virtualPresence = await privacyInteractionClient.connectVirtualPresenceWithRefreshToken({
  refreshToken: 'refresh-token-value',
  providerIdentity: 'user@gmail.com',
  scopes: ['https://www.googleapis.com/auth/gmail.readonly'],
})
```

{% endtab %}
{% endtabs %}

### Rescanning a Virtual Presence <a href="#subscribing-to-virtual-presence-updates" id="subscribing-to-virtual-presence-updates"></a>

Once a virtual presence is connected and the initial scan completes, you can trigger a rescan to discover new data holders that have appeared since the last scan. A rescan uses the stored credential - no new authorization from the user is required unless the credential has been revoked or expired. In this case, the virtual presence transitions to `NEEDS_REAUTH` and your application should prompt the user to re-authenticate.

Rescanning is incremental. New data holders are added to the existing set, and previously discovered data holders have their metadata updated. Existing data holders are never removed by a rescan. The virtual presence transitions to `SCANNING` while the rescan is in progress and returns to `CONNECTED` when complete.

To perform a rescan with default options, call the `rescanVirtualPresence` method with virtual presence's unique identifier. You can control the scope of a rescan by providing `ScanOptions`. This is useful for limiting how far back the scan reaches or excluding domains and categories the user is not interested in.

{% tabs %}
{% tab title="Typescript" %}

```typescript
try {
  const virtualPresence = await privacyInteractionClient.rescanVirtualPresence({
    id: virtualPresenceId,
    options: {
      earliestScanDate: '2026-01-01T00:00:00.000Z',
      maximumItemsProcessed: 200,
      excludeDomains: ['example.com', 'internal.org'],
    },
  })
} catch (error) {
  // Handle/notify user of errors
}
```

{% endtab %}
{% endtabs %}

**Input Parameters:**

| Parameter | Type          | Required | Description                                              |
| --------- | ------------- | -------- | -------------------------------------------------------- |
| `id`      | `string`      | Yes      | The unique identifier of the virtual presence to rescan. |
| `options` | `ScanOptions` | No       | Options to control the rescan behavior.                  |

**Scan Options:**

| Parameter               | Type       | Description                                                                                                        |
| ----------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------ |
| `maximumItemsProcessed` | `number`   | Maximum number of source items (e.g., emails) to process during the scan. When omitted, a service default is used. |
| `earliestScanDate`      | `string`   | Only consider items on or after this date (ISO 8601 string). Useful for narrowing the scan to recent activity.     |
| `latestScanDate`        | `string`   | Only consider items on or before this date (ISO 8601 string).                                                      |
| `excludeDomains`        | `string[]` | Domains to exclude from discovery results. Data holders matching these domains will be skipped.                    |
| `excludeCategories`     | `string[]` | Provider-specific categories or labels to exclude from processing (e.g., to skip personal emails).                 |

### Subscribing to Virtual Presence Updates <a href="#subscribing-to-virtual-presence-updates" id="subscribing-to-virtual-presence-updates"></a>

To receive real-time notifications when a virtual presence changes state, see [Subscriptions](https://docs.sudoplatform.com/~/revisions/U8P5YQbTWlABRqHUlUfP/guides/privacy-interaction/subscriptions).
