#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
and version-pinned across workflows. 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 header that renders from config alone,
- a table of the project's tasks, fed by a page-data
$graphqlsnapshot, - an actions widget exposing an "Add task" command (an inline
upsertEntity).
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 header rendered live:
{
"widgetName": "header",
"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.
Registering the provider company is a one-time onboarding step performed by a platform operator, not a regular customer call. The general API exposes read access to companies (
GET /access/external-companies); the create path lives on the operator surface and is done as part of onboarding before you wire the integration.
#6. User provisioning
When the provider is configured to auto-create users, the first POST /apps/run
for a new provider user (the integration is identified by app-context headers,
not a path segment) provisions the tenant user. 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). Cross-tenant data reads need both a share and a grant. See Shares & Grants.
#8. GraphQL → workflow
The table's data comes from a $graphql query over Task (queried via
POST /entity/graphql; root fields are PascalCase, operators are
underscore-prefixed, and filterable attributes nest under attributes:). The
same query can be turned into a workflow queryEntity action step with the
interactive converter at /graphql/ — paste a GraphQL query and
copy the emitted config straight into a workflow. See
Querying & GraphQL.