#Apps (Bridge)
A bridge app is a config-driven UI — dashboards, pages, and widgets — that renders inside a host provider (e.g. a shipping platform's canvas) and reads/writes your Helm entities on behalf of that provider's users. You author an app spec once; each tenant activates it, wires an integration to a provider organisation, and the provider's users get a rendered, permission-scoped app.
An app lives in two layers:
- Definition — the design-time app spec (
spec_document): dashboards, pages, widgets, providers, per-principal config schemas, policies, and shares. Authored under/definition/specsand shareable to other tenants. - Activation — a tenant's use of a spec: turning it on, registering provider
integrations, minting app tokens, and per-principal config. Driven under
/apps/*. Each activation is independent, so the same spec serves many tenants.
Authenticate with a Personal Access Token (HTTP Basic auth) — enter it once in the Authorize bar at the top of this page and every Try it below uses it. Each example body is pre-filled; the console targets staging by default. Full request/response schemas are in the linked API reference.
This guide is for an implementer who authors an app once and activates it for
other tenants. It walks the activation happy path; the
spec_document shape is detailed below. For the widgets see
the Widget Catalogue; for the expression model see
Smart Values. How a spec and its manifest provision a
consumer tenant is covered in Shares & Grants.
#Build an app
The flow is: (optional) register config schemas → create the spec → activate → register the provider organisation (one-time onboarding) → wire an integration → mint a token → render.
#1. (Optional) Register a principal-config schema
If your spec validates per-principal config (principalConfigSchemas), register
the JSON schema in the activating tenant first. Skip this when all three of
company/team/user are null.
POST/schema/schemasRegister a principal-config schemaAPI docs ↗Try it
#2. Create the app spec
App specs are part of the definition layer (POST /definition/specs). Inside
spec_document, every key is camelCase (defaultDashboard, widgetComponent,
autoCreateUsers, …); the one exception is the identity extractor keys
(user_external_id, …), which are well-known provider-identity field names.
POST/definition/specsCreate an app specAPI docs ↗Try it
The response is 201 with the spec under data. The policies and shares
blocks are optional. The full policy/share story is in
Shares & Grants; every spec_document part is detailed in
App spec structure.
#3. Activate the spec in a tenant
Activation is per tenant: there is exactly one activation per (spec, tenant), so
the call is an upsert — first call creates, later calls update. On creation, the
spec's policy block is provisioned into the activating tenant so its users get the
access the spec intends (see Shares & Grants).
PUT/apps/{specId}/configActivate the spec in your tenantAPI docs ↗Try it
To override the spec-defined policy, pass policy_id (an existing policy) or
policy (an inline definition); the override takes precedence and later spec edits
no longer touch this activation.
#4. Register the provider organisation (one-time onboarding)
An app renders for a specific provider organisation — the host platform's
tenant, keyed by (provider, external_id), where provider matches a key in
spec_document.providers. Registering that organisation is a one-time
platform-operator onboarding step, not something a tenant-admin can self-serve:
on the public surface /access/external-companies is read-only. Ask your Helm
operator to register the organisation against the activating tenant, then read it
back to get its id for the next step. See Provisioning.
#5. Wire an integration
An integration ties a registered provider organisation to your activation. Its
allowed_origins is both the render-time Origin check and the browser
CORS source for /apps/run — a browser host must list its exact origin here.
POST/apps/{specId}/integrationsCreate a provider integrationAPI docs ↗Try it
#6. Mint an app token
App tokens are scoped to a single integration and authenticate the render call.
The first issue needs no flag; replacing a live token is explicit
(?rotate=true — a plain PUT over an active token returns 409).
PUT/apps/{specId}/integrations/{integrationId}/tokenMint an app tokenAPI docs ↗Try it
The response carries raw_token (helm_at_<64-hex>) exactly once — it is shown
once and not retrievable later, so capture it now.
#7. (Optional) Pre-author principal config
Override spec defaults at the company, team, or user level before first use,
addressed by natural key. For company the principalId is the provider
organisation's id; for user it is the user's id.
PUT/apps/{specId}/principals/{type}/{principalId}Set per-principal configAPI docs ↗Try it
#8. Render the app
The render call uses the app token (Bearer), not your PAT, and includes the
integration id in the path — so it is not part of the Authorize bar above. The
provider posts the identity payload your spec's identity extractors expect:
curl -X POST https://api.helm.bridge-labs.com/apps/run/<integration_id> \
-H "Authorization: Bearer helm_at_<your_token>" \
-H "Content-Type: application/json" \
-d '{
"user": { "id": "ext-user-1", "emailAddress": "user@example.com", "firstName": "Jane", "lastName": "Doe" },
"company": { "id": "sedna-org-12345", "name": "Acme" }
}'
The 200 carries surfaces (the rendered dashboard), state, and referenceData.
A data_grid's rows come back under referenceData keyed by the grid's element id —
not inline in the surface.
#App spec structure
The spec_document is a declarative tree — dashboards → pages → widgets — plus
the providers, policies, and shares the app binds to. Inside it, every key is
camelCase, and the body is validated on create.
spec_document
├── dashboards.<key> a named dashboard (the unit a user opens)
│ └── pages.<key> a page within the dashboard
│ └── widgets.<id> a widget on the page (see the Widget Catalogue)
├── widgets.<id> SPEC-LEVEL widgets — merged into every page
├── pages.<key> SPEC-LEVEL shared pages — merged into every dashboard
└── providers, policies, shares, jobs, …
Merge semantics. Spec-level widgets/pages are inherited by every
dashboard; a dashboard-level entry of the same key overrides the shared one
(so navigate targets resolve against the merged set — no duplicate-modal idiom).
| Field | Req | Purpose |
|---|---|---|
dashboards |
✓ | The dashboards, keyed by name. The only required field. |
defaultDashboard |
Which dashboard renders first (literal key or a {{ }}); falls back to the first key. |
|
widgets / pages |
Spec-level widgets/pages merged into every dashboard (see merge semantics). | |
providers |
External data/identity providers the app binds to (auth, autoCreateUsers, identity). |
|
policies |
Named access policies; each is provisioned into every consumer tenant on activation. | |
defaultPolicy |
Default policy key (must be in policies); applied to the activation after provisioning. |
|
shares |
Explicit cross-tenant shares — the derived owner manifest is built solely from this block. | |
jobs |
Named jobs callable from a job step. |
|
principalConfigSchemas |
Maps company/team/user to a JSON-schema name its per-principal config must validate against. |
|
functions |
Spec-defined JSONata functions registered into every expression in this app. | |
defaults / messages |
Spec-level config defaults and error-copy overrides. |
Access is policy-based, not role-based — there is no top-level
rolesfield. App access ispolicies+defaultPolicy(provisioned per consumer tenant) plus per-user assignment. Apps that don't need policy-mode authorisation omitpoliciesand fall back to standard mode (the union of the user's role bindings);principalConfigSchemasgoverns config shape, not access.
#Widget behaviour & smart values
Widgets are config-driven and can carry interactive steps (entity ops, HTTP,
setPrincipalConfig), render-time $if visibility, and {{ }} smart values. The
full authoring model lives in Smart Values; each widget's
config is catalogued in the Widget Catalogue. Two
spec-wide authoring vocabularies — data sources (how a widget gets its rows) and
commands (what an action does) — are documented next.
#Data sources
A widget that shows data declares a dataSource — a data_grid/table's rows,
or a select/form's options. Pick one of four producers, optionally combined
with $pipe row-shaping and $dependsOn cross-widget refetch:
| Producer | Shape | Use |
|---|---|---|
$query |
{ "$query": { "type": "Task", "filter"?: {…}, "sort"?: [{…}], "include"?: [...], "single"?: true } } |
Read entities of a type (filter/sort mirror the entity query). |
$http |
{ "$http": { "url": "…", "method"?: "GET", "headers"?: {…}, "rowsPath"?: "data.items" } } |
Fetch rows from a provider HTTP endpoint (SSRF-safe; JSON only). |
$rows |
{ "$rows": [ … ] } |
An inline / computed array (often { "$jsonata": "page.data.x" }). |
$typeSchema |
{ "$typeSchema": { "entityType": "Vessel", "attribute": "segment" } } (or { "schema", "path" }) |
An option feed built from a schema's enum values. |
Combine a producer with these node-level siblings:
$pipe— shape the resolved rows (filter/map/orderBy/leftJoin/distinct). The full vocabulary is in Smart Values → Shaping a row set.$dependsOn—[ { "widget": "<id>", "field": "<key>" } ]: refetch this source when another widget's value changes (e.g. a grid that filters by a select).single— on$query, return the first row (ornull) instead of a one-element array.
"dataSource": {
"$query": {
"type": "Task",
"filter": { "operator": "eq", "path": ["attributes", "status"], "value": "open" },
"sort": [ { "path": ["attributes", "due_date"], "direction": "asc" } ]
},
"$dependsOn": [ { "widget": "status_filter", "field": "value" } ],
"$pipe": [ { "map": { "title": "{{ attributes.title }}", "due": "{{ attributes.due_date }}" } } ]
}
#Commands
An interactive widget (a button, a form submit, a row action) runs a command — an
ordered list of typed steps. Each step has a kind plus its own fields, and may
carry stepId (binds its result to outputs.<stepId>), if (a render-time gate),
and onSuccess / onError recovery sub-sequences (the same model as a workflow
action). The vocabulary:
kind |
Key fields | What it does |
|---|---|---|
navigate |
page, params?, modal? |
Go to a page (or open it as a modal). |
refresh |
includeModal? |
Re-render the current page. |
setPageData |
data, inModal? |
Write keys into page state (read elsewhere as page.data.*). |
setWidgetState |
widgetId, state |
Set one widget's state (selection, paging, …). |
notify |
level, message |
Show a toast. |
actions |
actions[], input?, output? |
Run orchestration actions inline — the inline-job path for apps. |
job |
job, input? |
Run a named job definition. |
setPrincipalConfig |
principalType, principalId?, config? / ops? |
Write per-principal config (company/team/user). |
setSecret |
target, value |
Store a scoped secret from inside the app (see Secrets). |
debug |
value? |
Echo a value while authoring. |
The actions step runs the same action catalog a workflow job does, inline in
the render request — prefer it over a named job step for app-local logic (see
Workflows → Jobs & actions).
"command": [
{ "kind": "actions", "actions": [ { "stepId": "save", "name": "upsertEntity", "config": { "typeName": "Task", "payload": "{{ form }}" } } ] },
{ "kind": "notify", "level": "success", "message": "Saved" },
{ "kind": "refresh" }
]
#Providers, tokens & secrets
- Providers & external users — auto-provisioning, identity extraction, and OIDC vs bearer providers. See Provisioning.
- App tokens — issue, rotate (
?rotate=true), list, and revoke per integration; revocation propagates immediately. - Secrets — set a value, then wire its reference into config (the reference is never stored in the spec). See Secrets.
#Share an app with another tenant
Publishing a spec for another tenant to install — spec shares, data shares, and the policy fan-out — is its own topic. See Shares & Grants.
#API reference
Every app spec, config, integration, token, and principal-config operation — with full schemas and an interactive console — is in the API reference.