Skip to content

External Approvers

External approvers are people outside your organisation — vendors, clients, contractors, counter-parties — who need to act on a workflow step but do not have an Aptiwise account.

They receive a one-time email link and can approve, reject, acknowledge, or sign directly from that link. No login, no account registration.


The encrypted token in the email link encodes {instance_id, step_id, approver_email}. No session or login is required — the token is the sole auth mechanism.


Set employee_type: external on the approver block to mark the step as public-facing:

workflow:
vendor_review:
type: decision
approval_type: needs_to_approve # or needs_to_sign, needs_to_acknowledge
approver:
email: "vendor@example.com"
employee_type: external
on_approve:
end_workflow: true
on_reject:
end_workflow: true

All four approval types work for external approvers:

| approval_type | Effect | |-----------------|--------| | needs_to_approve | External party approves or rejects | | needs_to_sign | External party draws and submits a signature | | needs_to_acknowledge | Non-blocking — external party acknowledges; workflow continues immediately | | receives_a_copy | Non-blocking — external party is notified; no action required |


If the approver's email is not known at design time, collect it in the form and reference it with ${form.<field_name>}:

form:
fields:
- name: vendor_name
type: text
label: Vendor Contact Name
required: true
- name: vendor_email
type: email
label: Vendor Email Address
required: true
help_text: "This person will receive a link to review and sign — no account needed"
workflow:
internal_approval:
type: decision
approval_type: needs_to_approve
approver:
role: "${requestor.manager}"
on_approve:
continue_to: vendor_sign
on_reject:
end_workflow: true
vendor_sign:
type: decision
approval_type: needs_to_sign
approver:
email: "${form.vendor_email}"
name: "${form.vendor_name}"
employee_type: external
on_approve:
end_workflow: true
on_reject:
end_workflow: true

The ${form.vendor_email} expression is resolved at step-creation time from the instance's form data.

See docs/examples/external-approver-dynamic-email.yaml for a complete annotated example.


Every approver action — including public ones — is tied to an employees row for audit purposes. When an external approver opens their link, the system resolves their record in this order:

| State | What happens | |-------|-------------| | Active employee with that email exists in the company | Record reused — no duplicate created | | Inactive employee with that email exists in the company | Record reused — avoids a UNIQUE (email, company_id) constraint error | | No record exists | A minimal employee_type = external record is auto-created |

This resolution is applied for all action types (approve, sign, acknowledge). You can safely use ${form.vendor_email} even if the vendor was previously registered or their account was deactivated.


POST /services/v1/approvals/public/{token}/action

Body:

{
"action": "approve",
"comments": "Reviewed and approved."
}

Works for all approval types. The token identifies the instance, step, and approver email.

External approvers who need to sign can draw their signature before or after acting:

GET /services/v1/signatures/public/{token} → check if a saved signature exists
POST /services/v1/signatures/public/{token} → save a drawn signature (base64 PNG)

See Signatures for the full signature canvas and field reference.


Auto-created records are minimal:

| Field | Value | |-------|-------| | name | Derived from email (alice.smith@example.comAlice Smith) | | employee_type | external | | is_active | true | | user_id | null — no system account |

Formally inviting the person later via the admin panel upgrades their record (adds user_id, full profile) without losing their approval history or saved signature.


See also: