Overview
In Flowra, multi-tenancy means two separate layers:- 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.
- External user — An end user of your product (for example, each customer in a SaaS app). Send
x-usernameon 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).
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:- Create a link session for alice (
x-username: alice). - Run workflows with the same header so Slack tools use alice’s account.
- Store
flowra_username = alicein your own database.
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):projectIdcomes 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_idin the payload must match the connected account’seUID(otherwise the event is skipped).
Knowledge and Table — per external user
Yes — Knowledge and Table are scoped pereUID, 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) usesprojectIdandownerEUID— you only see collections owned by the current external user. - Creating knowledge registers
ownerEUIDon collection metadata in Qdrant. - Chunks are filtered by
projectIdandcollectionId; 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 byprojectIdandownerEUID. - Creating a collection sets
ownerEUIDto the caller’seUID. - 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 markedexposeViaMcp. - Aggregations prepend
$matchonprojectId+collectionId;$lookupinto 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 samex-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 inx-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).
Related
- Authentication —
x-api-keyandx-username - Connected accounts — OAuth per user
- Workflows —
ownerEUIDand execution - Threads — per-user conversations
- Recipe: Multi-tenant basics
- Quickstart — first request