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

# Data Scopes

> Restrict which tables an embed key can query and enforce row-level isolation for multi-tenant deployments

## Overview

A **data scope** defines the exact slice of your database that an embed key is allowed to access. Instead of granting an embed key unrestricted access to every table in your data source, you create a named scope that specifies:

* Which **tables** the key may query
* Which **row-level filter** should be applied so that each caller only sees their own rows

Scopes are created independently of embed keys and then assigned to a key during key creation. One scope can be shared across many keys.

<Note>
  A `scopeId` URL parameter is required whenever an embed key has a data scope
  assigned to it. Requests without a valid `scopeId` will be rejected with a
  `403` error.
</Note>

## Creating a Scope

<Steps>
  <Step title="Open the Scopes page">
    Navigate to **Developer** > **Channels** and click **Create Scope** (or open the Scopes tab if visible).
  </Step>

  <Step title="Enter scope details">
    Provide a **Name** (required) and an optional **Description**.

    | Field           | Required | Description                                                                                                                              |
    | --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
    | **Name**        | Yes      | A short, human-readable label used to identify the scope when assigning it to embed keys (e.g. `Customer read-only`, `Outlet dashboard`) |
    | **Description** | No       | Free-text note that helps team members understand what data this scope exposes                                                           |

    Click **Next** when done.
  </Step>

  <Step title="Configure data access">
    This step has two parts: **Row-level scope** and **Table access**.

    ### Row-level scope

    Row-level scoping restricts each caller to only the rows that belong to them. It is required for multi-tenant deployments where different end-users must not see each other's data.

    | Setting          | Description                                                              |
    | ---------------- | ------------------------------------------------------------------------ |
    | **Scope table**  | The table that owns the tenant identifier (e.g. `outlets`)               |
    | **Scope column** | The primary column in that table that identifies each tenant (e.g. `id`) |

    At query time, Cobi automatically filters every query so that only rows matching the caller's `scopeId` value are returned.

    <Tip>
      If all callers should see the same data (e.g. a public read-only widget),
      leave row-level scoping unconfigured and rely on table access restrictions
      alone.
    </Tip>

    ### Table access

    Select which tables this scope can query. Tables are grouped to help you make the right choices:

    | Group              | Description                                                                                                                                                                 |
    | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Related tables** | Tables that contain a foreign key referencing the scope column. These are typically the safest to include because row-level filtering applies to them automatically         |
    | **Other tables**   | Tables without a direct relationship to the scope column. Queries against these tables will not be row-filtered, so include them only when full-table access is intentional |

    Use the **Filter tables** search box to find specific tables quickly.

    Click **Next** when done.
  </Step>

  <Step title="Review and create">
    The **Review scope** screen summarises your configuration:

    | Field         | Value                                                                                             |
    | ------------- | ------------------------------------------------------------------------------------------------- |
    | **Name**      | The scope name you entered                                                                        |
    | **Tables**    | Number of tables included                                                                         |
    | **Row scope** | The `table.column` pair used for filtering, and a note that it is *filtered by caller's scope ID* |

    Click **Create scope** to save. The scope is immediately available for assignment to embed keys.
  </Step>
</Steps>

## Assigning a Scope to an Embed Key

When creating or editing an embed key, the **Security** step includes a **Data scope** dropdown:

| Option                     | Behaviour                                                                                                          |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **Full access (no scope)** | The key can query all tables enabled on the data source. No `scopeId` parameter is required or used                |
| **A named scope**          | The key can only query the tables and rows defined in that scope. A `scopeId` must be supplied in every iframe URL |

<Warning>
  Selecting a scope on an embed key is permanent for that key. To change the
  scope, create a new embed key with the desired scope and revoke the old one.
</Warning>

## Using scopeId in the Iframe URL

When an embed key has a data scope assigned, every iframe `src` must include the `scopeId` parameter. The value should be the identifier of the tenant or entity whose data the current user is allowed to see — typically the primary key value from the scope table.

```html theme={null}
<iframe
  src="https://app.hellocobi.com/embed/chat?key=cobi_embed_your_key_here&scopeId=outlet_42"
  style="width: 100%; height: 600px; border: none; border-radius: 8px;"
  allow="clipboard-write"
  title="Cobi Chat"
></iframe>
```

Cobi uses `scopeId` to filter all queries at the row level. In the example above, queries against the `outlets` scope table would automatically include `WHERE outlets.id = 'outlet_42'`, and all related table queries would be filtered accordingly.

<Note>
  Render the `scopeId` server-side from your own authentication system. Never
  derive it from user-supplied input or client-side storage, as this would allow
  a user to spoof another tenant's ID.
</Note>

### What happens without scopeId

If a request is made to an embed key that has a scope assigned but no `scopeId` is provided, Cobi returns a `403` error and the widget displays an access-denied message. This is intentional — a missing `scopeId` is treated as an unauthorised request.

## Managing Scopes

From **Developer > Channels** (Scopes tab) you can view all scopes along with the number of tables and their row-scope configuration.

| Action     | How                                                                                                                                                           |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **View**   | Click on a scope to see its full configuration                                                                                                                |
| **Delete** | Open the scope and click **Delete**. Any embed key currently using this scope will lose its data access restrictions — review key assignments before deleting |

<Warning>
  Deleting a scope does not automatically revoke the embed keys assigned to it.
  Those keys will revert to having no scope, potentially exposing more data than
  intended. Audit your embed keys after any scope deletion.
</Warning>

## Security Considerations

* **Scope isolation** — each embed key can only be assigned one scope. There is no way for a key to escalate beyond its assigned scope at runtime.
* **Row-level enforcement** — Cobi enforces row-level filtering server-side. It cannot be bypassed by modifying the iframe URL or the chat query.
* **scopeId is not a secret** — the `scopeId` value is visible in the iframe `src`. It should only identify *which tenant*, not authenticate the request. Authentication is handled by the embed key itself. Always combine `scopeId` with domain restrictions (allowed origins) in production.
* **Minimal table access** — follow the principle of least privilege: only include the tables your widget actually needs. Excluding sensitive tables from the scope is the safest way to prevent unintended data exposure.
