Expose App Data to end-users
An end-user must never start a raw platform type such as data.appdata.record.write. V2 runners load one library at a time; the identity path cannot import App Data classes. The supported path is:
- Compose a virtual workflow whose steps are marked end-user eligible (record write/query/update/delete,
transaction.apply, document ops). - Expose it with
identity.app.expose-virtual-workflow. - The browser starts only
virtual.<composition_uuid>@<version>with the JWT from@orkestia/auth.
@orkestia/auth JWT → start virtual.<uuid>@<ver> → engine injects end-user
→ App Data forces owner / workspace
Expose
identity.app.expose-virtual-workflow({
identity_app_uuid: "…",
composition_uuid: "…",
version: 1,
})
Eligibility is read from the workflow catalog (end_user_eligible), not by importing the step class. After an App Data library release, the catalog must be refreshed from a process that can import those classes — otherwise every step looks ineligible and expose fails.
Every step in the composition must be eligible. Sensitive bindings (which table, which connection) are fixed at expose time. The user supplies only free inputs (a filter, a page size).
Invoke as the user
import { createOrkestiaAuth } from "@orkestia/auth"
import { LtIntegWorkflowsClient } from "@ltinteg/workflows-sdk"
const auth = createOrkestiaAuth({ clientKey: "orkestia_…" })
const session = auth.getSession()
const client = new LtIntegWorkflowsClient({
baseUrl: "https://workflow-api.orkestia.dev",
token: session.token,
})
const run = await client.start("virtual.<composition_uuid>@1", {
/* free inputs only */
})
const { state_data } = await run.wait()
Same idea over REST: POST https://workflow-api.orkestia.dev/api/workflows/start with Authorization: Bearer <end-user JWT>.
You can also let an agent use the App Data MCP as that user — still no SQL, still the same isolation. A process that already speaks PostgREST can use PostgREST HTTP with the same JWT. Operators who need SELECT use Query, never the end-user token.
Org-side seed
Members do not write owner-scoped rows. Shared catalogs go through appdata.publish-as-app into app-owned tables. Retract with appdata.retract-as-app.
Why this is safe
No DSN in the app
The frontend never holds a database secret.
Principal injected
The user id is taken from the verified token. It cannot be overridden.
Only what you exposed
An end-user token can start only exposed virtuals, never raw platform workflows.
This is the same pattern as End-user data, spelled out for App Data.
