Skip to main content

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.
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).

Two layers of tenancy


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.

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).
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.

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):

External user level (eUID + x-username)


Common flows

SaaS: each customer connects their own Slack

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 and the External users API section above.

Multiple workflows for multiple users

  • 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

When searching threads, filter metadata with the same identifier you set at creation. See 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.

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.

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.
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.

Practical rule

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

External users — API

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

1

Project and API key

Create a project in the Dashboard and keep the API key on your server only.
2

User mapping

Define a stable username for each end user in your system (for example tenantId_userId).
3

OAuth connection

Call POST /connected_accounts/link with x-username — one connection per user per service.
4

Workflows and agents

Create and run workflows with the same x-username so ownerEUID is correct.
5

Chat

Create threads with user metadata; send the same x-username on all Graphify requests.
6

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).

Common mistakes


Architecture overview


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).

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).