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

# Multi-Tenant Isolation of Code Snippets

> Isolate cached code snippets per customer with workspaces

<Note>
  Multi-tenant isolation is available on **Pro** and **Enterprise** tiers.
</Note>

<Card title="Talk to us" icon="calendar" href="https://calendly.com/raymond-raysurfer/15min">
  Want private code snippets for your team? Book a 15-minute call to get set up.
</Card>

## Overview

If you're building a product that serves multiple customers, you need their
cached snippets to be completely isolated. Customer A should never see
Customer B's code.

Raysurfer workspaces provide this isolation. Each workspace is a completely
separate cache — snippets uploaded to one workspace can never be retrieved
from another.

***

## Setup

Pass your customer's ID as `workspace_id`:

<CodeGroup>
  ```python Python theme={null}
  from raysurfer import RaysurferClient
  from claude_agent_sdk import ClaudeAgentOptions

  options = ClaudeAgentOptions(allowed_tools=["Read", "Write", "Bash"])

  # Each customer gets their own isolated workspace
  async with RaysurferClient(options, workspace_id="acme") as client:
      await client.query("Process shipment data")
      async for msg in client.response():
          print(msg)
  ```

  ```typescript TypeScript theme={null}
  import { RaysurferClient } from "raysurfer";

  // Each customer gets their own isolated workspace
  const client = new RaysurferClient({
    allowedTools: ["Read", "Write", "Bash"],
    workspaceId: "acme",
  });

  for await (const msg of client.query("Process shipment data")) {
    console.log(msg);
  }
  ```
</CodeGroup>

That's it. All uploads and retrievals are now isolated to that customer.

***

## Low-Level API

For direct snippet management, pass `workspace_id` either at the client level or per-method:

<CodeGroup>
  ```python Python theme={null}
  from raysurfer import AsyncRaySurfer
  from raysurfer.types import FileWritten

  # Option 1: Set workspace_id at client level
  client = AsyncRaySurfer(api_key="rs_...", workspace_id="acme")
  results = await client.search(task="Process shipment data", top_k=5)

  # Option 2: Set workspace_id per-method (overrides client level)
  client = AsyncRaySurfer(api_key="rs_...")
  results = await client.search(task="Process shipment data", top_k=5, workspace_id="acme")

  # Upload - stores only to customer's isolated workspace
  await client.upload(
      task="Process shipment data",
      file_written=FileWritten(path="processor.py", content="def process(): ..."),
      succeeded=True,
      workspace_id="acme",  # Per-method override
  )
  ```

  ```typescript TypeScript theme={null}
  import { RaySurfer } from "raysurfer";

  // Option 1: Set workspaceId at client level
  const client = new RaySurfer({ apiKey: "rs_...", workspaceId: "acme" });
  const results = await client.search({ task: "Process shipment data", topK: 5 });

  // Option 2: Set workspaceId per-method (overrides client level)
  const client2 = new RaySurfer({ apiKey: "rs_..." });
  const results2 = await client2.search({ task: "Process shipment data", topK: 5, workspaceId: "acme" });

  // Upload - stores only to customer's isolated workspace
  await client2.upload({
    task: "Process shipment data",
    fileWritten: { path: "processor.ts", content: "function process() { ... }" },
    succeeded: true,
    workspaceId: "acme",  // per-method override
  });
  ```
</CodeGroup>

***

## Example: Multi-Tenant App

<CodeGroup>
  ```python Python theme={null}
  from raysurfer import RaysurferClient
  from claude_agent_sdk import ClaudeAgentOptions

  async def handle_customer_request(customer_id: str, task: str):
      options = ClaudeAgentOptions(allowed_tools=["Read", "Write", "Bash"])

      # Pass the customer ID as workspace_id
      async with RaysurferClient(options, workspace_id=customer_id) as client:
          await client.query(task)
          async for msg in client.response():
              yield msg
  ```

  ```typescript TypeScript theme={null}
  import { RaysurferClient } from "raysurfer";

  async function* handleCustomerRequest(customerId: string, task: string) {
    // Pass the customer ID as workspaceId
    const client = new RaysurferClient({
      allowedTools: ["Read", "Write", "Bash"],
      workspaceId: customerId,
    });

    for await (const msg of client.query(task)) {
      yield msg;
    }
  }
  ```
</CodeGroup>

***

## FAQ

### Can snippets leak between workspaces?

No. Workspaces are completely isolated. There is no code path that allows
cross-workspace access.

### What if I don't set a workspace\_id?

Your organization's shared cache is used. All users in your org see the same
snippets — fine for internal use, but not for multi-tenant scenarios.

### What does a workspace actually control?

A workspace is just a cache partition — it siloes which cached code snippets
are visible, nothing more. Raysurfer does not manage your underlying
permissions (database access, column-level security, API scopes, etc.). Your
application decides which `workspace_id` to assign each user based on whatever
permission model you already have.

For example, if user A can query columns M and N, and user B can query columns
M, N, O, and P, give them different workspace IDs (e.g. `"mn"` and `"mnop"`).
User A's cached scripts will only ever reference their allowed columns, and
user B's are completely separate. Raysurfer guarantees no cross-workspace
leakage — your app still enforces the actual data permissions.

### What should I use as the workspace\_id?

Any string that uniquely maps to a permission boundary — a customer ID, a role
name, a hash of allowed scopes, or a permission profile key. It's an arbitrary
string you choose.

### How does workspace search work?

When you provide a `workspace_id`, Raysurfer searches both the workspace
namespace and the org-wide namespace in parallel, merges results by score,
and returns the top results. This means workspace-specific snippets compete
on equal footing with company-wide snippets.

### Can I query multiple workspaces at once?

No. Each client is scoped to one workspace. Query separately if needed.
