Authorize

#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 by key.
  • Task{ key, title, state, priority }, identified by key, with state and priority indexed so they're filterable/sortable.
  • Assignee{ email, name }, identified by email.

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:

  1. Inline job — an upsertEntity action that creates a "Kickoff" Task for the new project, authored directly on the workflow step.
  2. Referenced job — invokes the reusable complete-task job (defined once under /definition/jobs), which marks that task done.

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 $query entry in the page's dataSources map whose rows are shaped flat by a $pipe map step (the table reads row[field], so nested attributes.* values are lifted to top-level keys — see Smart Values → Shaping a row set),
  • an actions widget exposing an "Add task" command: an inline actions step running upsertEntity (the same action catalog a workflow job uses), then a notify toast and a refresh.

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:

Loading live widget preview…
{
  "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)):

  1. PUT /apps/{specId}/config — the tenant's app config.
  2. Register the provider company, then POST /apps/{specId}/integrations — the integration that callers run against.
  3. 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's company_external_id extractor reads) and lists it back with GET /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.