Skip to content

External Vendor Auto-Provision Workflow

A step-by-step guide to building a workflow that auto-provisions (or reuses) a Floh user for an external vendor contact and grants a time-bound role with no approval gate. Access is bounded by the workflow:provision_users permission check inside user_create and by durable role expiry (role_grant.expiresAt + the role-expiry-check scheduler), not by a manual sign-off.

This matches the External Vendor Auto-Provision starter in the New Workflow gallery. You can instantiate that starter and skip to Step 6 — Publish, or rebuild it by hand below.

Prerequisites

  1. A vendor role the contact should receive for the access window, created under PeopleRoles.
  2. The workflow:create, workflow:update, workflow:publish, and workflow:read permissions on your admin account (the Workflows tab and GET /api/workflows are gated on workflow:read), and template:read if you instantiate the starter from the gallery. The account that runs this workflow also needs workflow:start and workflow:provision_usersuser_create refuses to insert an unconfirmed user without the latter, and every outcome is captured in a workflow.user_auto_provisioned audit row.

Security model — read this before publishing

  • Do not wait out the access window with the delay connector. delay is an in-process setTimeout. A day-scale wait keeps the run running until stuck-run recovery (default 30 minutes) fails it — leaving the grant in place with no revoke. Use role_grant.expiresAt and let the role-expiry-check scheduler expire the assignment. See the Time-Bound Group Access starter for the same pattern.
  • Fail closed on duplicate grants. role_revoke and the expiry job key assignments by user+role, not by "the assignment this run created". Default onDuplicate: "skip" would let a second run succeed and a later expiry would tear down a pre-existing assignment this workflow never owned. Set onDuplicate: "error".
  • Fail closed on partial entitlement provision. findExpired() only selects active assignments. Default failOnPartial: false leaves a partially_provisioned row whose surviving entitlements never expire. Set failOnPartial: true.
  • Validate duration, email, and display name before provisioning. A zero/negative expiresInDays, a boolean/array/object expiresInDays (Number(true) === 1 and Number([365]) === 365 would otherwise provision), a non-string vendorEmail, or a provided non-string vendorDisplayName (start payloads type variables as unknown; an array or object JSON-stringifies into a malformed account / display_name) must not create a Floh user. Put that check in a transform that runs before user_create. When vendorDisplayName is omitted, the transform must write it as an empty string so interpolation does not persist the literal {{vendorDisplayName}} token; user_create then stores the email as display_name. Workflows already instantiated from an older copy of this starter keep the previous transform until you re-instantiate from the gallery.
  • There is no generic locked-variable flag. expiresInDays has to be a workflow variable so the transform sandbox can read it (vars.expiresInDays); template-parameter substitution skips script keys on purpose (see .cursor/rules/server/sandbox-security.mdc). The instantiated number is the variable default. A catalog publisher who needs the field hidden from requesters should omit it from the published input form. Callers who can start the run can still override it via the start API.

Overview

flowchart TD
  Start[Start]
  Compute["Validate email and compute expiry"]
  Provision["Provision or reuse vendor account"]
  Grant["Grant vendor role with expiresAt"]
  NotifyOk[Notify sponsor — granted]
  NotifyFail[Notify sponsor — failed]
  Success[End: Success]
  Failure[End: Failure]

  Start --> Compute
  Compute -->|success| Provision
  Compute -->|error| NotifyFail
  Provision -->|success| Grant
  Provision -->|error| NotifyFail
  Grant -->|success| NotifyOk
  Grant -->|error| NotifyFail
  NotifyOk --> Success
  NotifyFail --> Failure

Step 1 — Create the workflow

Navigate to Design in the sidebar (opens /workflows), open the Workflows tab, and click New Workflow:

Field Value
Name External Vendor Auto-Provision
Description Provision a vendor user and grant a time-bound role with no approval gate
Category general
Error Strategy stop
Trigger manual

Leave Subject Variable unset. general workflows do not take a subject.

Step 2 — Variables

Name Type Required Default Description
vendorEmail string yes Vendor contact to provision or reuse.
vendorDisplayName string no Used only when a new Floh user must be created.
expiresInDays number yes (from template parameter) Days until the granted role expires.

Step 3 — Build the graph

  1. Validate input and compute access expiry (transform) — reject a non-string vendorEmail, a provided non-string vendorDisplayName, a boolean/array/object expiresInDays, and a non-positive expiresInDays. Numeric strings such as "7" are accepted. Write omitted vendorDisplayName as "", then write accessExpiresAt as an ISO timestamp (Date.parse(now()) + days * 24 * 60 * 60 * 1000). Read vars.expiresInDays, vars.vendorEmail, and vars.vendorDisplayName; do not interpolate those values into the script source. error → failure notification.
  2. Provision or reuse vendor account (user_create) — email: "{{vendorEmail}}", ifExists: "reuse". Exposes userCreateUserId / userCreateEmail on success. error → failure notification.
  3. Grant vendor role (role_grant) —
Config key Value
roleDefinitionId the vendor role
userId {{userCreateUserId}}
expiresAt {{accessExpiresAt}}
onDuplicate error
failOnPartial true

There is no later role_revoke and no delay step. error → failure notification.

  1. Notify sponsor that access is active (notification) — internal recipient. The body should include {{accessExpiresAt}} so the sponsor knows when the assignment will expire.
  2. Notify sponsor of provisioning failure (notification) — same recipient; include {{lastStepError.stepId}} and {{lastStepError.message}}.
  3. Two end nodes: success and failure.

Step 4 — Parameters (if you save this as a template)

Parameter Required Purpose
sponsorUserId yes Notification contact on grant and on failure. Not an approver.
vendorRoleId yes Role granted for the access window.
expiresInDays yes Days until expiry. Substituted into the expiresInDays variable default.

Step 5 — Connectors

This graph uses no connectors. Expiry is the role-assignment scheduler, not the built-in delay connector.

Step 6 — Publish

Save, then Publish. Start a run with a test email, confirm a Floh user is created (or reused), confirm the role assignment has an expires_at timestamp, and confirm a second start for the same vendor

  • role fails closed (onDuplicate: "error") rather than silently skipping.