Identity & Multi-Tenancy
TL;DR
- Two identity planes. Members (your team, and AI actors with agent tokens) operate the platform. End-users (your app's customers) sign in with "Sign in with Orkestia" and can only run what you expose.
- Org scoping is automatic. Your organization is resolved server-side from your token. You never pass
organization_uuidby hand.whoamitells you who you are. - End-user isolation is a three-part key: org, app, end-user, all derived from the verified token.
- Provisioning an identity app is one workflow call (
identity.app.provision). An assistant can do it by followingrule://orkestia-auth-setup. New apps start indev(localhost). App Host needslive. - Signing keys (
identity.key.*) are not members. nsec is for Buzz owner AUTH. Invite teammates in Settings → Members. - Seats are a hard login cap with forgiving semantics: over-cap users can register but not log in until a new pack lands.
Keeping the two planes apart is the key to understanding who can see what. They authenticate differently, are scoped differently, and are billed differently. Everything else in the platform hangs off an organization.
The two identity planes
| Members | End-users | |
|---|---|---|
| Who | Your team, plus AI actors | Your application's users |
| Sign in via | Org login (hosted) or an API / agent token | "Sign in with Orkestia" (OIDC + PKCE) |
| Operate | The platform: console, API, SDKs, MCP | Only the app you built |
| Scope | The whole organization | Their own data within your app |
| Token | Org JWT, API token, or agt_ agent token | End-user JWT (user_type: end_user, RS256) |
| Provisioned by | Org onboarding, invite, or Staff hire | Self-registration into your app |
Organization plane
Members act across the org. Connections, runs, compositions, and resources belong to the organization and are isolated from every other org.
End-user plane
Your app's users act only within your app. Orkestia pins their identity to each run so they can never reach another user's data or any org-level resource.
Organizations and members
An organization is your workspace, the unit everything is scoped to. Members are the people and AI actors who operate it. Members manage connections, run and author workflows, build compositions, govern Staff, and configure the org.
The decisive property is that org scoping is automatic. Your organization is resolved server-side from your credentials and applied to every run. You do not pass an org id into initial_data unless a schema explicitly declares one, and then it must match your authenticated org. This one rule is what isolates one customer's resources, data, and runs from another's.
whoami()
→ { "user_id": "…", "organization_uuid": "b7f343…", "username": "you@example.com", "token_type": "access" }
start_workflow("aws.s3.create_bucket", { "bucket": "reports", "connection_uuid": "1f2a…" })
→ Orkestia stamps organization_uuid = b7f343… onto the run. Passing it by hand is rejected as an unknown field.
whoami additionally returns agent_uuid, staff_actor_uuid, permission_mode, and seat_mode. See Staff governance and Billing, Pricing & Seats.One person, many organizations
Membership is many-to-many. The same user can belong to several organizations with one active organization at a time. Every credentialed surface (console, API, SDKs, MCP) acts as your active org. Switching re-scopes everything: connections, runs, catalogs, Staff, billing. The scoping rule is unchanged: whichever org is active is resolved server-side and stamped onto every run.
End-users: "Sign in with Orkestia"
When you build an app on Orkestia, its users are end-users, not members. They authenticate through "Sign in with Orkestia": hosted login at login.orkestia.dev that your app embeds. None of the dangerous parts live in your code:
- OIDC + PKCE authorization-code flow. The token never rides in a redirect URL.
- RS256 JWTs signed by Orkestia's rotating key, verifiable against a published JWKS.
- MFA (TOTP), email verification, and password reset built in.
A decoded end-user token carries an explicit type marker so it can never be confused with a member token:
{
"iss": "login.orkestia.dev",
"user_type": "end_user",
"sub": "<end_user_uuid>",
"org": "<organization_uuid>",
"app": "<client_uuid>",
"kid": "prod-1"
}
When a signed-in user invokes one of your exposed workflows or compositions, Orkestia injects the user's identity immutably and enforces that the run only touches that user's data.
sequenceDiagram
participant U as End-user
participant App as Your frontend
participant O as Sign in with Orkestia
participant E as Workflow engine
U->>App: open app
App->>O: authorize (OIDC + PKCE)
O->>U: login / MFA / verify
O-->>App: end-user JWT (user_type=end_user)
App->>E: invoke exposed workflow + JWT
Note over E: org + app + end_user pinned from the token
E-->>App: result scoped to this user only
The isolation tuple: org, app, end-user
| Dimension | Where it comes from | What it isolates |
|---|---|---|
| org | The app's registration | One customer from another |
| app | The client_uuid of the identity app | One of your apps from another |
| end_user | The sub claim, injected immutably | One of your users from another |
The engine derives all three from the verified token, not from inputs your frontend supplies, so a user cannot widen their own scope by editing a request.
Seats and the hard login cap
End-user capacity is sold in seat packs. The cap is a hard login cap, not a soft throttle:
- Below the limit, end-users register and log in normally.
- Beyond the limit, end-users can still be created, but they cannot log in. The org owner is notified until another pack is purchased.
Exposing workflows to end-users: App Enablement
End-users do not get the full catalog. A member must explicitly expose a workflow or composition to an app before any end-user can invoke it.
One call to provision, and an assistant can run it
Provisioning an identity app is a single workflow. The MCP server publishes the recipe as rule://orkestia-auth-setup, so an assistant can wire it unattended:
whoami()
start_workflow("identity.app.provision", {
"name": "My App",
"redirect_uris": ["http://localhost:5173/callback", "https://myapp.com/callback"]
})
→ { client_key, client_uuid, redirect_uris,
integration: { issuer, discovery_url, authorize_url, code_exchange_url, jwks_url, flow, sdk } }
That returns everything needed to wire auth: client_key is the public PKCE client id (safe in browser source), the origins are accepted immediately, and integration carries the endpoints. Add more redirect URIs later with identity.app.configure-client. Wire the client side with @orkestia/auth (signIn, handleCallback, silent renew), then pass session.token to the Node or Python workflow SDK. App rows live in App Data.
To let end-users run business logic scoped to themselves, expose a composition:
start_workflow("identity.app.expose-virtual-workflow", {
"identity_app_uuid": "<from provision>",
"composition_uuid": "<a composition you authored>", "version": 1
})
The app then POSTs to /api/workflows with the end-user JWT as a Bearer token. End-users may start only the virtual workflows you exposed, nothing else.
Exposure is a descriptor, not a boolean
end_user_eligible is a capability descriptor authored alongside the workflow. It declares which inputs are bindable by the app versus sensitive, the workflow's side-effect class (read, write-own, external-send, irreversible), and what must be bound before exposure is legal. The app fills a policy within that envelope, and the engine enforces both at invocation time.
- Workflow authordeclares descriptor
- Member exposesto an app
- App sets policywithin envelope
- End-user invokes
- Engine enforcesdescriptor + policy + tuple
- Run scoped to user
- Rejected
- Workflow author→Member exposes
- Member exposes→App sets policy
- App sets policy→End-user invokes
- End-user invokes→Engine enforces
- Engine enforces→ allowed →Run scoped to user
- Engine enforces→ violation →Rejected
Chat for your end-users
An identity app can also get a chat space. End-users open it by signing in with the same app identity, the seat cap applies as it does at login, and a Staff actor bound to an end-user seat in the app can answer inside the conversation. The chat's end-user actions are entry points exposed on the app, like any other exposed composition. See Chat.
Everything is org-scoped
The same organization_uuid that gates a member's run also partitions:
Ask your AI assistant
Call whoami and explain what kind of principal I am, which org I'm scoped to, and whether I'm on a user, API, or agent token.
List my organization's members and pending invitations.
Follow rule://orkestia-auth-setup to provision an identity app called "Demo" with redirect URI http://localhost:5173/callback. Return the client_key and integration endpoints.
Expose composition <composition_uuid> version 1 to identity app <identity_app_uuid>. Confirm the side-effect class before you do it.
For AI agents
| Rule | Detail |
|---|---|
whoami first | Identity and org come from the token. See rule://authenticated-context. |
| Never pass the org | Do not add organization_uuid to initial_data unless the schema declares it. Cross-org access is not supported. |
| End-user setup is a recipe | Follow rule://orkestia-auth-setup: identity.app.provision, then optionally identity.app.expose-virtual-workflow. |
| Live vs dev | New apps are dev (localhost). identity.app.set-mode → live is one-way. App Host claim/publish require live. |
| One AgentConfig per app | A second agent product is a second Identity app. |
client_key is public | It is a PKCE client id, safe in browser source. There is no client secret to protect. |
| Members vs end-users | Org members are the customer's team. End-users are the app's customers. Do not conflate them. |
| Signing keys are not members | identity.key.* (nsec) is for Buzz owner AUTH. Invite people in Settings → Members. |
Status and current limitations
Next
Lumen Observability
Orkestia's telemetry store and triage engine, JSON HTTP ingest, SHA-256 error groups, traces, metrics, the query API, and the Lumen MCP server for assistant-driven triage
Billing, Pricing & Seats
How an Orkestia organization is billed, the platform subscription, seats for humans and AI actors, end-user seats, the execution and request meters, add-ons, and per-agent budgets that bound what an AI workforce can spend
