Orkestia
Blog
Core Concepts

Identity & Multi-Tenancy

Two identity planes, org members who operate the platform and end-users who sign in to apps you build, with automatic org scoping, per-user isolation, and a one-call setup an assistant can run for you

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_uuid by hand. whoami tells 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 following rule://orkestia-auth-setup. New apps start in dev (localhost). App Host needs live.
  • 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

MembersEnd-users
WhoYour team, plus AI actorsYour application's users
Sign in viaOrg login (hosted) or an API / agent token"Sign in with Orkestia" (OIDC + PKCE)
OperateThe platform: console, API, SDKs, MCPOnly the app you built
ScopeThe whole organizationTheir own data within your app
TokenOrg JWT, API token, or agt_ agent tokenEnd-user JWT (user_type: end_user, RS256)
Provisioned byOrg onboarding, invite, or Staff hireSelf-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.

as an assistant sees it
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.
Members can be users (humans) or keys (AI agents and service principals). Both are first-class actors and both occupy a seat. On an agent token, 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

DimensionWhere it comes fromWhat it isolates
orgThe app's registrationOne customer from another
appThe client_uuid of the identity appOne of your apps from another
end_userThe sub claim, injected immutablyOne 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.

Orkestia paves the data layer; it does not police a database it does not run. For workflows whose side effects run inside Orkestia-managed runners, connections, and App Data, the platform enforces the scope end to end. When a workflow reads or writes rows in your own store, Orkestia guarantees the verified identity reaching it and ships row-policy templates, but the final row-level isolation is enforced by the policies you apply. See Security & compliance.

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.
Signups are never silently dropped, and you cannot accidentally run an unbounded, unbilled population. New seats unlock already-registered users immediately.

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:

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

Full walkthrough: provisioning, "Sign in with Orkestia", and exposing per-user data, in App Enablement.

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:

Connections & runners

Cloud accounts, runners, and credentials belong to the org and are never visible cross-tenant.

Workflows & state

Runs, event-sourced state, and compositions are stored and queried per org.

Observability

Lumen telemetry, audit logs, and drift signals are partitioned by org.

Identity apps & end-users

Every identity app, end-user, and seat pack nests under one org.

Ask your AI assistant

prompts
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

RuleDetail
whoami firstIdentity and org come from the token. See rule://authenticated-context.
Never pass the orgDo not add organization_uuid to initial_data unless the schema declares it. Cross-org access is not supported.
End-user setup is a recipeFollow rule://orkestia-auth-setup: identity.app.provision, then optionally identity.app.expose-virtual-workflow.
Live vs devNew apps are dev (localhost). identity.app.set-mode → live is one-way. App Host claim/publish require live.
One AgentConfig per appA second agent product is a second Identity app.
client_key is publicIt is a PKCE client id, safe in browser source. There is no client secret to protect.
Members vs end-usersOrg members are the customer's team. End-users are the app's customers. Do not conflate them.
Signing keys are not membersidentity.key.* (nsec) is for Buzz owner AUTH. Invite people in Settings → Members.

Status and current limitations

"Sign in with Orkestia" is live in beta and proven end to end. In flight: federation (Google, GitHub) runtime, production email delivery, per-route rate limiting, and a Lumen dashboard for auth events. Exact endpoints and seat-pack sizes may change; see reference.orkestia.dev.

Next

Add auth & per-user data to your app

Provision an identity app and expose scoped workflows.

App Data

Where those users' rows live.

App Host

Claim a live site. Signing keys are not members.

Get your team onboarded

Stand up an org and invite members.

Build compositions to expose

The logic your end-users will run.

Connect an AI assistant

Let an assistant provision and expose for you.