Recurring workflows
A yearly supplier check, a monthly attestation, an annual review: the same workflow, run again and again for the same person. In Clipless that is one workflow and one session per run. The period is a label on the session; the workflow itself knows nothing about years.
Start this period's session
bash
curl -X POST "$CLIPLESS_URL/api/v1/workflows/supplier_check_v1/sessions" \
-H "authorization: Bearer $CLIPLESS_API_KEY" -H 'content-type: application/json' \
-d '{ "role": "role:supplier", "externalUserId": "acme", "period": "2026" }'periodis any label you choose:"2026","2026-09","2026-Q3".- There is one session per workflow, environment, user and period. Asking again returns the session that exists, with
"created": falseand a fresh token for the role you asked for. So a daily job can call this for every supplier without keeping track of who has started. - A
periodneeds anexternalUserId— it is someone's run. - One-off workflows leave
periodout.
List a user's history or everyone's 2026 run:
bash
bunx clipless sessions supplier_check_v1 --user acme
bunx clipless sessions supplier_check_v1 --period 2026Start from last year's answers
Most of a supplier's details do not change. Start the new run from the last one:
json
{ "role": "role:supplier", "externalUserId": "acme", "period": "2026", "prefillFrom": "previous" }"previous" is that user's latest completed run of the workflow; you can also name a session id (it must be the same user's). If there is none, the session simply starts empty.
What carries over:
The answers of stages owned by the role that owns the first stage — the supplier's own answers. A reviewer's decision is never carried: it is made fresh each run.
Not fields marked
carryOver={false}— a confirmation or a signature is given again every time:tsx<Field name="attest" type="boolean" label="The details above are current" mustBeTrue carryOver={false} />Across revisions: if the workflow changed since last year, a field carries only if it still exists with the same type.
Vault answers are carried and re-encrypted under the new session's key — unless last year's session was erased, in which case they are gone.
prefill still works alongside, and wins where both set a field. The response says what happened:
json
"carried": {
"from": "Xk2…",
"fields": ["legal_name", "vat_number", "address", "bank_iban"],
"dropped": [
{ "field": "attest", "reason": "not carried" },
{ "field": "decision", "reason": "another role" }
]
}In the ledger, carried answers are field events from system, marked source: "carry" with from naming last year's session.
What changed since last year
Because the carried values are in the ledger, "what did the supplier change" is a comparison, not something you store:
tsx
function ChangedSinceLastYear() {
const { from, changes } = useCarriedChanges()
if (!from) return null
return (
<ul>
{changes.map((c) => (
<li key={c.field}>{c.field}: {String(c.before)} → {String(c.after)}</li>
))}
</ul>
)
}Show it to the supplier as they go, and to the reviewer, who then only has to look at what moved.
In the browser
Local mode has the same rules: carryAnswers(manifest, { manifest, answers }) picks what carries, and <Workflow carried={{ from, answers }}> starts a session with them. The supplier check in the playground runs this year after year.