Appearance
Private vs workspace apps
Most reads in the App SDK are filtered by what kind of app you are. There are two kinds:
- Workspace app — installed into, and locked to, a single workspace. The host knows its workspace id. Reads are scoped to that one workspace; cross-workspace requests are rejected.
- Private (personal) app — not bound to any workspace (e.g. the marketplace, personal tools). Reads span all workspaces the user belongs to, and a few workspace-management methods are only available here.
You can tell which one you're in from hailer.info(): a workspace app has a workspaceId; a private app does not.
What changes, method by method
The host applies the scope on its side, so it's the same call returning different data:
| Method | Workspace app | Private app |
|---|---|---|
workspace.list() | Just the app's workspace | All the user's workspaces (sorted by name) |
workspace.current() | The app's workspace | The user's currently-active workspace |
workspace.create(name) | Rejected (403) | Allowed |
workflow.list() | Workflows in the app's workspace | Workflows across all workspaces |
workflow.get(id) | Scoped to the app's workspace | Any workspace the user can see |
team.list() | Teams in the app's workspace | Teams across all workspaces |
user.get(id) | null if the user isn't in the app's workspace | Any user the app can see |
user.list() | Members of the current workspace | Members of the current workspace |
permission.map({workspaceId}) | Always the app's workspace; a different workspaceId is rejected (403) | All workspaces, or a specific one on request |
insight.list() | Insights in the app's workspace | Insights in the user's current workspace |
credential.list() / get(id) | Credentials the user created plus workspace credentials tagged with your app they may read (admins/owners: all of them) | Only credentials the user created |
credential.update(id, …) | Personal credentials only; a workspace credential is rejected (403) | Personal credentials only |
team.list() and permission.map() return data keyed by workspace id, so a private app's result can contain several workspaces at once — iterate the keys rather than assuming one.
Practical implications
Don't hard-code a single workspace. In a private app, results are multi-workspace. Read the workspace id off each item (workflows carry a workspaceId; teams/permissions are keyed by it) instead of assuming there's only one.
Guard workspace-management calls. workspace.create() throws in a workspace app. Only offer it when you're a private app:
ts
const { workspaceId } = hailer.info();
const isPrivateApp = !workspaceId;
if (isPrivateApp) {
const ws = await hailer.workspace.create('New workspace');
}Don't ask a workspace app about another workspace. permission.map({ workspaceId }) with a different id — and any cross-workspace read — is rejected with a 403 in a workspace app. Pass no workspaceId (or the app's own) and let the host scope it.
Credentials are app-bound in both scopes. credential.create() always stores a personal credential bound to your app — the host drops workspace scope and readers regardless of which kind of app you are, so a workspace app cannot create a credential its colleagues can read. Workspace credentials come from an admin tagging them to your app in workspace settings; a workspace app can list and get those (subject to the credential's readers — which workspace admins and owners bypass, seeing every workspace credential tagged with your app) but never update them. Because the vault is per-user either way, what list() returns for one user says nothing about another — don't cache a credential id as if it were shared.
Empty results reflect scope. A workspace app returning [] from workflow.list() means there are no workflows in that workspace. Check the app's scope with info().
Next steps
- App lifecycle — where
workspaceIdcomes from. - Reference: workspace · permission · user · credential