In a multi-tenant app, the org admin cannot open the console for every tenant. Instead, give each tenant admin a screen in your own app that manages their tenant. These calls use the token your app already has, and the tenant is always the one in the token — nothing in the request can point at another tenant.
Everything is under one route: /app/{orgCode}/tenant/{section}. Only a tenant admin (a user with full access in that tenant) can use it; everyone else gets 403.
| Section | What it manages |
|---|---|
settings | The plain-value groups of Tenant settings: general (the key/values your Logic Blocks, Glyphs and agents read as params.settings), ui, notifications. (The password rule for app users is org-wide, under Org Settings.) |
emailtemplates | The tenant's own email templates (a tenant template overrides the organization's template of the same name). |
featureflags | The tenant's changes to the organization's feature flags — see Per-tenant flags. |
members | Who is in the workspace and their status; approve someone waiting (PUT { user_id }), or remove or decline someone (DELETE ?user_id=). The last admin can't be removed (409). |
invitations | The invitations the workspace has sent (GET), and withdrawing one (DELETE ?id=). A used invitation can't be withdrawn (409). |
workspace | The workspace's own details (GET) and renaming it (PUT { name }, at most 80 characters). |
What is not here
Secrets, connector credentials, AI keys, SSO and the tenant's email server are set in the console (or their own routes), never through this route and never through MCP. Asking for one of those groups answers 400.
Settings
| Call | Does |
|---|---|
GET /app/{orgCode}/tenant/settings[?group=general][&name=config] | List this tenant's settings, optionally one group, or one setting by group and name. |
PUT /app/{orgCode}/tenant/settings { "group": "general", "name": "config", "value": { ... } } | Create or replace one setting. The whole value is replaced. POST does the same. |
DELETE /app/{orgCode}/tenant/settings?group=ui&name=theme | Delete one setting. |
// set the tenant's company name and currency; Logic Blocks read them as {{params.settings.company}}
await api.put(`/app/${org}/tenant/settings`, {
group: 'general', name: 'config', value: { company: 'Acme', currency: 'USD' },
}); The general group's setting named config is the one your Logic Blocks, Glyphs and agents see: {{params.settings.company}}, ctx.params.settings.company. Keep a single general entry: the assets read one, and a second is not merged.
Email templates
await api.put(`/app/${org}/tenant/emailtemplates`, {
name: 'welcome',
value: { subject: 'Welcome, {{name}}', html: '<p>Hello {{name}}</p>', text: 'Hello {{name}}' },
});
const { settings } = await api.get(`/app/${org}/tenant/emailtemplates`); A template needs a subject and an html body. The name is the join key the email connector's template input and system emails use.
From an AI assistant (MCP)
| Tool | Does |
|---|---|
list_tenant_settings, set_tenant_setting, delete_tenant_setting | The same settings, for a tenant you name. |
list_tenant_email_templates, set_tenant_email_template, delete_tenant_email_template | The tenant's own templates. |
list_tenant_feature_flags, set_tenant_feature_flag, clear_tenant_feature_flag_override | The tenant's flag changes. |
These need a tenant. If your org has more than one, the assistant asks which tenant you mean (it can list them with list_org_tenants). For all tenants at once use the org-level tools, such as set_email_template.
Good to know
| Situation | What you get |
|---|---|
| A user who is not a tenant admin | 403 "Tenant admin (wildcard ACL) required" |
| An unknown section | 404 with the list of sections |
| A group outside the section | 400 naming the groups that are allowed |
A value that is not an object, or too large | 400 |
| Changes | Take effect for the next run — Logic Blocks, Glyphs and agents read settings when they start. |