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

# Multi-tenancy

> How Flowra isolates your project and your end users — project scope, external users, x-username, and which resources are per-tenant

## Overview

In Flowra, **multi-tenancy** means two separate layers:

1. **Project** — Your workspace as the developer. An API key is bound to one project; almost all resources (tools, workflows, auth configs, knowledge, and more) live under that project.
2. **External user** — An end user of *your* product (for example, each customer in a SaaS app). Send **`x-username`** on requests to run in that user’s context so OAuth connections, workflows, and runs belong to the right person.

```mermaid theme={null}
flowchart TB
  subgraph you["You (developer)"]
    AK["API key"]
    PRJ["Project"]
    AK --> PRJ
  end

  subgraph endusers["Your product's end users"]
    U1["external user: alice"]
    U2["external user: bob"]
    U3["external user: carol"]
  end

  PRJ --> U1
  PRJ --> U2
  PRJ --> U3

  U1 --> CA1["Connected accounts alice"]
  U2 --> CA2["Connected accounts bob"]
  U3 --> CA3["Connected accounts carol"]
```

<Note>
  In this guide, **tenant** usually means an **external user** (an end user inside your project). **Project** is the main security boundary between different Flowra customers (developers).
</Note>

***

## Two layers of tenancy

| Layer             | Identifier                | How it is set                                                         | What it isolates                                                                                                             |
| ----------------- | ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **Project**       | `projectId`               | From the **API key** (required; clients cannot spoof another project) | Custom tools, auth configs, files, triggers, MCP servers, API keys                                                           |
| **External user** | `eUID` (internal UUID)    | From **`x-username`** (your name/username) → resolve or auto-create   | Connected accounts, workflows (`ownerEUID`), executions, threads, Composio, **Knowledge collections**, **Table collections** |
| **Both**          | `projectId` + `ownerEUID` | API key + `x-username` together                                       | Knowledge (RAG) and Table isolate per external user *inside* your project                                                    |

```mermaid theme={null}
sequenceDiagram
  participant App as Your app
  participant API as Flowra API
  participant DB as Database

  App->>API: x-api-key + x-username: alice
  API->>API: API key → projectId
  API->>API: x-username → resolve ExternalUser (eUID)
  API->>DB: All queries scoped by projectId (+ eUID when needed)
  DB-->>API: Only that project / that user's data
  API-->>App: Response
```

***

## Authentication and headers

### 1. `x-api-key` — project boundary

Each API key is bound to **one project**. Flowra derives `projectId` from the key; you cannot switch to another project via session or cookies when using API key auth.

See [Authentication](/authentication).

### 2. `x-username` — end-user context (optional)

On endpoints that support it, **`x-username`** is the same **name** as in the external-users API below (for example `customer_42` or an email).

| Case                         | Behavior                                                                                                                |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **No** `x-username`          | Flowra uses a default external user for the project (`id = projectId`) — useful for testing or project-level operations |
| **With** `x-username: alice` | If `alice` does not exist, she is created; her `eUID` is set on the request context                                     |

```bash theme={null}
curl -X POST "https://flowra.dev/api/v1/connected_accounts/link" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-username: customer_alice" \
  -H "Content-Type: application/json" \
  -d '{"toolkitSlug": "slack", "authConfigId": "..."}'
```

```mermaid theme={null}
flowchart LR
  H1["x-api-key"] --> P["projectId"]
  H2["x-username optional"] --> E["eUID"]
  P --> CTX["request.user"]
  E --> CTX
  CTX --> API["endpoints"]
```

<Warning>
  **Do not expose your API key in a public frontend.** The recommended pattern: your backend holds the API key and sends the appropriate `x-username` for each end user.
</Warning>

***

## What is scoped where?

### Project level (`projectId`)

These resources are **shared** across end users in the project (unless you explicitly filter per user in your own data):

| Resource                              | Description                                        |
| ------------------------------------- | -------------------------------------------------- |
| [Auth configs](/guides/auth-configs)  | OAuth/API key setup per toolkit — once per project |
| [Tools](/guides/tools) (and toolkits) | Global catalog plus project-specific custom tools  |
| [Files](/guides/files)                | Upload and list within the project                 |
| [Triggers](/guides/triggers)          | Trigger instances tied to workflows / project      |
| [MCP](/guides/mcp)                    | Project MCP servers (`projectId`)                  |
| API keys                              | Each key belongs to one project only               |

### External user level (`eUID` + `x-username`)

| Resource                                         | Description                                                                                            |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| [Connected accounts](/guides/connected-accounts) | Each OAuth connection has an `eUID`; list with `eUIDs` filter                                          |
| [Workflows](/guides/workflows)                   | Workflows have `ownerEUID`; list/run only that user's workflows                                        |
| Workflow execution                               | `execution.eUID` is separate per user                                                                  |
| [Knowledge (RAG)](/guides/knowledge)             | Collection metadata filtered by `projectId` + **`ownerEUID`**; `eUID` required for non-admin API calls |
| [Table](/guides/table)                           | Collection metadata filtered by `projectId` + **`ownerEUID`**; documents scoped via collection access  |
| [Threads (Graphify)](/guides/threads)            | Metadata uses effective `userId` (= `eUID` when present)                                               |
| Tool execution (Composio)                        | Uses the connected account for that `eUID`                                                             |

```mermaid theme={null}
flowchart TB
  subgraph projectScope["Project scope — x-api-key"]
    AC[Auth configs]
    FL[Files]
    TR[Triggers]
    MCP[MCP]
  end

  subgraph userScope["External user scope — x-username"]
    WF[Workflows]
    EX[Executions]
    CC[Connected accounts]
    KN[Knowledge ownerEUID]
    TB[Table ownerEUID]
    TH[Threads]
  end

  PRJ2[projectId] --> projectScope
  PRJ2 --> userScope
  EU[eUID] --> userScope
```

***

## Common flows

### SaaS: each customer connects their own Slack

```mermaid theme={null}
sequenceDiagram
  participant User as User alice
  participant YourApp as Your app
  participant Flowra as Flowra

  YourApp->>Flowra: POST /connected_accounts/link<br/>x-username: alice
  Flowra-->>YourApp: OAuth URL
  YourApp->>User: redirect to URL
  User->>Flowra: OAuth Slack
  Flowra->>Flowra: connected account → eUID alice

  User->>YourApp: Send a message
  YourApp->>Flowra: POST execute workflow<br/>x-username: alice
  Flowra->>Flowra: Slack with alice's token
```

**Steps:**

1. Create a link session for alice (`x-username: alice`).
2. Run workflows with the same header so Slack tools use alice’s account.
3. Store `flowra_username = alice` in your own database.

See [Connected accounts](/guides/connected-accounts) and the [External users API](/guides/multi-tenancy#external-users-api) section above.

### Multiple workflows for multiple users

```mermaid theme={null}
flowchart TD
  A["x-username: alice"] --> WA["Alice's workflows<br/>ownerEUID = alice eUID"]
  B["x-username: bob"] --> WB["Bob's workflows<br/>ownerEUID = bob eUID"]
  P["Same projectId / API key"] --> A
  P --> B
```

* **Create/list workflows** with `x-username` — you only see that external user’s workflows.
* **Without** `x-username` — the project’s default external user is used.

### Chat and threads

```mermaid theme={null}
flowchart LR
  U["x-username: support_ticket_99"] --> T["POST /graphify/threads<br/>metadata.userId = eUID"]
  T --> R["runs/stream on same thread"]
```

When searching threads, filter metadata with the same identifier you set at creation. See [Threads](/guides/threads).

### Triggers and webhooks

For event triggers (for example Telegram or Slack):

* `projectId` comes from the trigger instance or workflow.
* On project webhooks, Flowra may create a **per-thread external user** so each conversation has its own `eUID`.
* `user_id` in the payload must match the connected account’s `eUID` (otherwise the event is skipped).

See [Triggers](/guides/triggers).

***

## Knowledge and Table — per external user

Yes — **Knowledge and Table are scoped per `eUID`**, not only per project. Each collection is owned by an external user via **`ownerEUID`** (set from the request’s `eUID`, which comes from `x-username`). Non-admin API calls require a resolved `eUID`.

```mermaid theme={null}
flowchart LR
  H["x-api-key + x-username"] --> PID["projectId"]
  H --> EUID["eUID"]
  PID --> META["Collection metadata"]
  EUID --> META
  META -->|"ownerEUID match"| ACCESS["List / read / write collections"]
  ACCESS --> DATA["Chunks or documents in that collection"]
```

### Knowledge (RAG)

* **Listing collections** (`GET /knowledge/collections`) uses `projectId` **and** `ownerEUID` — you only see collections owned by the current external user.
* **Creating** knowledge registers `ownerEUID` on collection metadata in Qdrant.
* **Chunks** are filtered by `projectId` and `collectionId`; access to a collection is enforced through metadata ownership (and workflow allowlists).
* **Without** `x-username`, you get the project default external user — alice and bob would not get separate knowledge unless you send distinct usernames.

### Table (MongoDB)

* **Listing collections** (`GET /table/collections`) filters metadata by `projectId` **and** `ownerEUID`.
* **Creating** a collection sets `ownerEUID` to the caller’s `eUID`.
* **Reading/writing documents** requires access to that collection (`resolveMongoCollectionToolAccess`): only the **owner** can write; reads are allowed for the owner or, when invoked via MCP, collections marked **`exposeViaMcp`**.
* Aggregations prepend `$match` on `projectId` + `collectionId`; `$lookup` into tenant-isolated collections is blocked.

<Note>
  **MCP exception:** A collection with `exposeViaMcp: true` can be **read** by other external users in the same project when called through MCP. Writes still require ownership.
</Note>

### Practical rule

Use the **same `x-username`** for Knowledge, Table, workflows, and connected accounts for a given end user. Different usernames mean different `eUID`s and separate knowledge/table collections.

See [Knowledge](/guides/knowledge) and [Table](/guides/table).

***

## External users — API

| Operation                     | Endpoint                                         |
| ----------------------------- | ------------------------------------------------ |
| List all users in the project | `GET /api/v1/external-users`                     |
| Look up by username           | `GET /api/v1/external-users/username/{username}` |

Listing external users **always returns the whole project**; `x-username` does not change the list result on that endpoint. On **operational** endpoints (connect, execute, workflows, and so on), `x-username` switches the request context.

***

## Implementation checklist

<Steps>
  <Step title="Project and API key">
    Create a project in the [Dashboard](https://flowra.dev) and keep the API key on your server only.
  </Step>

  <Step title="User mapping">
    Define a stable `username` for each end user in your system (for example `tenantId_userId`).
  </Step>

  <Step title="OAuth connection">
    Call `POST /connected_accounts/link` with `x-username` — one connection per user per service.
  </Step>

  <Step title="Workflows and agents">
    Create and run workflows with the same `x-username` so `ownerEUID` is correct.
  </Step>

  <Step title="Chat">
    Create threads with user metadata; send the same `x-username` on all Graphify requests.
  </Step>

  <Step title="Knowledge and Table">
    Send `x-username` on Knowledge and Table API calls — collections are isolated by `ownerEUID`. Create separate collections per end user (or use `exposeViaMcp` only when you intentionally want cross-user MCP read).
  </Step>
</Steps>

***

## Common mistakes

| Mistake                                                           | Consequence                                                             | Fix                                                                                         |
| ----------------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Omitting `x-username` in production                               | Everyone shares the project default external user                       | Send the header on every user-scoped request                                                |
| Different `x-username` for link vs execute                        | Workflow uses another user’s account                                    | One stable username per end user                                                            |
| API key in the browser                                            | Full project tenant leak                                                | Backend only                                                                                |
| Same API key, no `x-username`, expecting per-user Knowledge/Table | All users share the project default `eUID` and see the same collections | Send `x-username` per end user                                                              |
| Expecting to read another user’s Table/Knowledge collection       | Access denied (unless MCP + `exposeViaMcp`)                             | One collection owner per `x-username`; use `exposeViaMcp` only when shared read is intended |
| Listing connected accounts without `eUIDs`                        | All users’ accounts returned                                            | Use `?eUIDs=<uuid>` or resolve username first                                               |

***

## Architecture overview

```mermaid theme={null}
flowchart TB
  subgraph client["Your client"]
    BE[Backend + API key]
  end

  subgraph flowra["Flowra"]
    GW[API Gateway / Auth]
    LU[Load user: projectId + eUID]
    SVC[Services: workflow, tools, graphify, ...]
    DB[(PostgreSQL / Mongo / Qdrant)]
    GW --> LU --> SVC --> DB
  end

  subgraph identity["Identity"]
    KEY[API key → projectId]
    XU[x-username → ExternalUser → eUID]
  end

  BE -->|x-api-key| GW
  BE -->|x-username| GW
  KEY -.-> LU
  XU -.-> LU
```

***

## External users API

**External users** are the end-user identities inside your project. You list them and look them up by username; use the same username in **`x-username`** on user-scoped calls.

### List external users

**GET** `/api/v1/external-users` — all external users for the project (`id`, `name`, `projectId`).

```bash theme={null}
curl -X GET "https://flowra.dev/api/v1/external-users" \
  -H "x-api-key: YOUR_API_KEY"
```

### Get by username

**GET** `/api/v1/external-users/username/{username}` — one user by name, or null if missing.

For **GET** `/external-users` (list), the response is the same with or without `x-username` — you get all users for the project. The header matters on endpoints that run in a specific user’s context (connected accounts, workflows, knowledge, table, and similar).

***

## Related

* [Authentication](/authentication) — `x-api-key` and `x-username`
* [Connected accounts](/guides/connected-accounts) — OAuth per user
* [Workflows](/guides/workflows) — `ownerEUID` and execution
* [Threads](/guides/threads) — per-user conversations
* [Recipe: Multi-tenant basics](/recipes/multi-tenant-basics)
* [Quickstart](/quickstart) — first request
