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 https://api.clipless.dev/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 playground
The supplier check in the playground runs this year after year: each run starts from the last, with what changed shown to the supplier and the reviewer.