#Canonical Example — the Tasks domain
This is the full worked example. It threads the concept guides together on one tiny, generic domain so you can see how a data model, a workflow, an app spec, and cross-tenant sharing fit into a single build. Each step links to the guide that covers it in depth; the snippets here carry only the concrete Tasks requests unique to the example.
Everything is a live API call against https://api.helm.bridge-labs.com,
authenticated with a Personal Access Token over HTTP Basic auth
(base64(client_id:client_secret)).
The domain is intentionally minimal:
Project ──< Task >── Assignee
#1. Data model
Three entity types, each with a JSON attribute schema and a stable identifier key:
Project—{ key, name, status }, identified bykey.Task—{ key, title, state, priority }, identified bykey, withstateandpriorityindexed so they're filterable/sortable.Assignee—{ email, name }, identified byemail.
Two relationship rules connect them: ProjectToTask (one-to-many) and
TaskToAssignee (many-to-one). The flow is always schema → entity type →
relationship rule → entities → links — see
Entities & Data Model for the full mechanics.
Register the Task attribute schema first:
POST/schema/schemasRegister the Task schemaAPI docs ↗Try it
Then bind it to an entity type, declaring the key attribute as its stable
identifier key (what a bulk upsert matches an existing row on) and which
attributes are indexed:
POST/entity/entity-typesCreate the Task entity typeAPI docs ↗Try it
Repeat for Project and Assignee, then create the two relationship rules
(/entity/relationship-rules) that link them.
#2. Workflow — an inline action and a referenced job
The workflow onboard-project runs two job steps:
- Inline job — an
upsertEntityaction that creates a "Kickoff"Taskfor the new project, authored directly on the workflow step. - Referenced job — invokes the reusable
complete-taskjob (defined once under/definition/jobs), which marks that taskdone.
Inline keeps step-local logic next to the workflow; a referenced job is reusable
across workflows, and which job the reference runs is a
binding each environment can decide for itself. Smart
values thread the trigger payload through both steps. See
Workflows and Smart Values
— a step's output binds as outputs.<stepId>.
First the reusable job:
POST/definition/jobsDefine the complete-task jobAPI docs ↗Try it
Then the workflow that creates the kickoff task inline and calls that job:
POST/definition/workflowsDefine the onboard-project workflowAPI docs ↗Try it
#3. Activation & monitoring
A definition is draft until it's activated for your tenant — activation
merges env, reconciles the trigger rows, and binds the access policy each run
acts under. It's idempotent. See
Triggers.
POST/workflows/{id}/activateActivate onboard-projectAPI docs ↗Try it
Each invocation produces a run you can inspect. List runs filtered by workflow:
GET/workflows/runs/workflowsList runs for a workflowAPI docs ↗Try it
Pass workflow_id as a query parameter, then drill into a single run for its
per-job and per-action detail via GET /workflows/runs/workflows/{id}.
#4. App spec — a dashboard with live widgets
The app spec (the spec_document body on the wire) defines one dashboard,
Tasks, with a board page composed of three widgets:
- a headline that renders from config alone,
- a table of the project's tasks, fed by a named
$queryentry in the page'sdataSourcesmap whose rows are shaped flat by a$pipemapstep (the table readsrow[field], so nestedattributes.*values are lifted to top-level keys — see Smart Values → Shaping a row set), - an actions widget exposing an "Add task" command: an inline
actionsstep runningupsertEntity(the same action catalog a workflow job uses), then anotifytoast and arefresh.
The full widget set — light widgets that render from config and heavy widgets that need authored sample data — is documented, with live previews, in the Widget Catalogue. Here is the headline rendered live:
{
"widgetName": "headline",
"config": {
"text": "Tasks",
"headerLevel": 1
}
}
See App spec structure for
every spec_document part.
#5. App activation (config, integration, token)
Activating the spec for a tenant creates the activation chain (see Apps (Bridge)):
PUT /apps/{specId}/config— the tenant's app config.- Register the provider company, then
POST /apps/{specId}/integrations— the integration that callers run against. PUT /apps/{specId}/integrations/{integrationId}/token— the run token, captured once.
Register the provider organisation first. A tenant admin registers it with
POST /access/external-companies(provider+external_id, keyed to what your spec'scompany_external_idextractor reads) and lists it back withGET /access/external-companies; the integration then binds it to your activation.
#6. User provisioning
When the provider is configured to auto-create users (autoCreateUsers), the
first render for a new provider user provisions the tenant user from the spec's
identity block — see
Apps → Providers & identity.
You can then assign that user to a spec policy
(PUT /apps/{specId}/users/{authUserId}/policy). See
Provisioning.
#7. Shares & grants
To let a principal read Tasks within your tenant, create an intra-tenant
grant (tier-2) on the Task entity type:
POST/access/grantsGrant read on the Task typeAPI docs ↗Try it
To let another tenant build on this — activate the shared spec, or store their own Tasks of the shared type — the owner adds a read-only share (tier-1); the consumer activates it in their own tenant. Cross-tenant data reads need both a share and a grant. See Share Workflows & Apps, Activate Shared Workflows & Apps, and Shares & Grants.
#8. Query → workflow
The table's data comes from an engine-native $query source over
QuickstartTask — the same filter/sort/include vocabulary as
POST /entity/query (see
Entities & Data Model → Read & query).
The same data is also reachable over GraphQL (POST /entity/graphql; root
fields are PascalCase, operators are underscore-prefixed, and filterable
attributes nest under attributes:), and a GraphQL query can be turned into a
workflow queryEntity action step with the interactive converter at
/graphql/ — paste a query and copy the emitted config straight
into a workflow. See Querying & GraphQL.