REFRACT Workflow API Reference
HTTP endpoint reference for creating workflow sessions, submitting steps, managing approvals, and querying workflow status.
All REFRACT Workflow endpoints are hosted on the Dispatcher Azure Function. Initiator and approver routes require a valid Entra ID JWT: a signed-in user's token with the user_impersonation scope, or an app-only token with the Render.Access permission (see Who may call). Approve, reject, request changes, escalate, and the Entra path of a governance decision refuse an app-only token with 403 and code APP_CANNOT_DECIDE: decisions are made by signed-in people. Participant portal routes use a portal access token, sent as Authorization: Bearer <pt>. A token in the URL (?pt=) is refused with 401 LINK_TOKEN_IN_URL. The participant's link carries a link reference (/w/<linkReference>), not the token: the portal resolves the reference and sends the token to the Dispatcher on the participant's behalf.
Base URL
All paths are relative to your Dispatcher Function base URL, e.g. https://<your-dispatcher>.azurewebsites.net.
Create Workflow Session
POST /api/workflowCreates a new workflow instance and returns a portal URL for the participant.
Auth: Entra ID JWT (initiator)
Request body:
{
"templatePath": "templates/vendor-onboarding.rpkg",
"correlationId": "vc-2026-0601-acme",
"templateSettings": {
"workflow": {
"schemaPath": "Workflows/vendor-onboarding.yaml",
"recipients": [
{ "email": "vendor@contoso.com", "displayName": "Vendor Contact" }
],
"requireOtp": false,
"organizationName": "Contoso Ltd",
"message": "Please complete the vendor onboarding form."
}
},
"options": {
"documentId": "VENDOR-2026-001",
"docType": "vendor-contract"
}
}| Field | Type | Required | Description |
|---|---|---|---|
templatePath | string | Yes* | Path to the .rpkg template. Used for the full render engine at seal time. Also accepted as templateId for legacy callers. |
correlationId | string | No | Caller-supplied identifier. Auto-generated UUID if absent, and honoured if supplied - unlike a plain render request, workflow creation keeps the caller's own value. |
templateSettings.workflow.schemaPath | string | No | Explicit path to the workflow schema YAML (e.g. Workflows/vendor-onboarding.yaml). When absent, schema is discovered from structured_content nodes inside the .rpkg template. |
templateSettings.workflow.recipients | array | No | One or more recipients who receive the portal invitation email. |
templateSettings.workflow.requireOtp | boolean | No | When true, participant must verify an OTP before accessing workflow steps. Default false. |
templateSettings.workflow.organizationName | string | No | Organization name shown in notification emails. |
templateSettings.workflow.message | string | No | Custom body text included in the invitation email. |
templateSettings.workflow.reminders | object | No | Automatic reminder settings: enabled, firstAfterDays, repeatEveryDays, maxReminders. On by default; set only the fields you want to change. Out-of-range or non-integer values are refused with HTTP 400 and code WORKFLOW_REMINDERS_INVALID. See Reminders. |
templateSettings.esign | object | No | REFRACT eSign settings to trigger automatically after the final PDF is sealed. Set enabled: true and provide signers, signingMode. See REFRACT eSign. |
options.documentId | string | No | Document identifier for audit and display (e.g. contract number, invoice ID). |
options.docType | string | No | Document type label (e.g. vendor-contract, expense-claim). |
initialData | object | No | Pre-populated field data for governance workflows with autoValidateOnCreate: true. Keys must match field key values in the schema. When provided, validation runs immediately and portal interaction is skipped. |
templateSettings.delivery does not currently send an invitation
Setting templateSettings.delivery on a workflow session is accepted, but the mode is not carried through to the post-seal delivery message, so no recipient email is sent. To deliver a sealed workflow PDF, call POST /api/delivery directly once the workflow reaches complete.
Schema path resolution
When templateSettings.workflow.schemaPath is set, the schema YAML is fetched directly from the SP assets library. When absent, REFRACT Workflow discovers the schema from structured_content nodes inside the .rpkg template file. Use schemaPath when your PDF layout template and workflow form schema are separate files.
Response 201:
{
"workflowId": "a3f1c2d4-8e9b-4f0a-b2c3-1d4e5f6a7b8c",
"correlationId": "vc-2026-0601-acme",
"state": "in-progress",
"portalUrl": "https://portal.refractplatform.com/w/<linkReference>"
}| Field | Description |
|---|---|
workflowId | UUID for this workflow session. Use this to poll status and perform approver actions. |
correlationId | Correlation ID (caller-supplied or auto-generated). |
state | in-progress (participant can begin entering data). |
portalUrl | Participant portal URL, in the form /w/<linkReference>. The reference is registered with the portal, which resolves it; the URL carries no token. The link lives at most 1 day, and the portal reissues a shorter-lived, 4-hour portal token once the participant is inside (see When a Session Ends). When the portal does not answer the registration (unreachable, a timeout, or a 5xx), the workflow is not created and the answer is 503 WORKFLOW_LINK_UNAVAILABLE with Retry-After: 60: send the same request again after the wait. When the portal refuses it, the workflow is not created and the answer is 502 WORKFLOW_LINK_NOT_REGISTERED. See When a Link Cannot Be Registered Yet. Forward to the participant via email or your own notification. |
Idempotency
Submitting the same correlationId twice returns the existing session (HTTP 200 with idempotent: true) rather than creating a duplicate.
Get Workflow (Portal Bootstrap)
GET /api/workflow/{workflowId}Returns the workflow instance, schema, and current step state for the participant portal.
Auth: Portal access token (Authorization: Bearer <pt>). An Entra ID JWT is also accepted from a signed-in participant of the workflow or a caller with the Lifecycle.Reader role; anyone else gets 403 with code ROLE_MISSING. An app-only token is never a participant, so it needs the role.
Response 200: Full portal payload including schema steps, current data snapshots, and - only when the workflow is validation-failed - validationResults.
Submit Step
PATCH /api/workflow/{workflowId}/step/{stepIndex}Saves the participant's field data for a single step. Can be called multiple times before the step is submitted.
Auth: Portal access token
Request body:
{
"responses": {
"vendor_name": "Contoso Supplies Ltd",
"invoice_total": 47500,
"payment_terms": "net30"
}
}The field is responses, an object keyed by field key.
Response 200: Updated step entity.
Advance Step
POST /api/workflow/{workflowId}/advanceMarks the current step as submitted and advances to the next step, or - on the final step - triggers schema validation. This route reads no request body; the current step's own entity must already be in submitted state (via the PATCH .../step/{stepIndex} call above) before advance will accept it.
Auth: Portal access token or Entra ID JWT
Preconditions (409 otherwise): the workflow must be in-progress or changes-requested, and the current step must already be submitted.
Response 200, mid-workflow:
{ "state": "in-progress", "currentStepIndex": 1, "portalToken": "..." }Response 200, final step, validation passed:
{ "state": "pending-approval", "allStepsComplete": true, "approverStages": [ "..." ], "hasWarnings": false }Response 200, final step, validation failed:
{
"state": "validation-failed",
"validationErrors": [
{
"ruleId": "po_amount_match",
"severity": "error",
"message": "Invoice total exceeds the approved PO amount.",
"fieldKey": "invoice_total"
}
],
"validationWarnings": []
}Approve Workflow
POST /api/workflow/{workflowId}/approveRecords the approver's decision and enqueues the seal message. The route does not read a request body - any comment sent is ignored.
Auth: Entra ID JWT of a signed-in approver. An app-only token gets 403 with code APP_CANNOT_DECIDE.
Response 200:
{ "workflowId": "a3f1c2d4-8e9b-4f0a-b2c3-1d4e5f6a7b8c", "state": "approved" }Reject Workflow
POST /api/workflow/{workflowId}/rejectRecords a rejection decision. No PDF is produced.
Auth: Entra ID JWT of a signed-in approver or reviewer. An app-only token gets 403 with code APP_CANNOT_DECIDE.
Request body:
{
"reason": "Vendor not on approved supplier list."
}reason is the only field read, and is optional with no minimum length.
Response 200:
{ "workflowId": "a3f1c2d4-8e9b-4f0a-b2c3-1d4e5f6a7b8c", "state": "rejected" }Open for Review
POST /api/workflow/{workflowId}/review-openMoves a pending-approval workflow to under-review and returns the full submission (steps, submitted data, validation results, change-request history) for the reviewer. Must be called before request-changes below.
Auth: Entra ID JWT from someone who reviews the workflow (a participant, the workflow administrator, an approver any stage names, or a member of the current stage's group), or a caller with the Lifecycle.Reader role. Anyone else in the tenant gets 403 with code ROLE_MISSING.
Request Changes
POST /api/workflow/{workflowId}/request-changesReturns the submission to the participant with structured change requests. Requires the workflow to already be under-review (call review-open first) - 409 otherwise. Workflow transitions to changes-requested.
Auth: Entra ID JWT of a signed-in person. An app-only token gets 403 with code APP_CANNOT_DECIDE.
Request body:
{
"comments": "Invoice total does not match attached invoice PDF.",
"changeRequests": [
{
"message": "Invoice total does not match attached invoice PDF.",
"fieldKey": "invoice_total",
"severity": "error"
}
],
"notifyParticipant": true
}| Field | Type | Description |
|---|---|---|
comments | string | Optional free-text note. If changeRequests is omitted, this becomes a single synthesized change request. |
changeRequests | array | Optional; the field is changeRequests, not changes. At least one of comments/changeRequests is required. |
changeRequests[].message | string | Human-readable correction instruction shown to participant. |
changeRequests[].fieldKey | string | Optional; links the change request to a specific field for portal highlighting. |
changeRequests[].severity | info | warning | error | Urgency indicator. |
notifyParticipant | boolean | Optional. |
Get Workflow Status
GET /api/workflow/{workflowId}/statusReturns the current state and key milestones.
Auth: Portal access token, or an Entra ID JWT from a signed-in participant of the workflow (its initiator included) or a caller with the Lifecycle.Reader role. An app-only token is never a participant, even of a workflow it created, so it needs the role. Without either, 403 with code ROLE_MISSING.
Response 200:
{
"workflowId": "a3f1c2d4-8e9b-4f0a-b2c3-1d4e5f6a7b8c",
"correlationId": "vc-2026-0601-acme",
"state": "pending-approval",
"currentStepIndex": 2,
"totalSteps": 2,
"title": "Vendor Onboarding",
"createdAt": "2026-06-09T10:00:00.000Z",
"updatedAt": "2026-06-09T10:05:00.000Z",
"completedAt": null,
"sealedBlobPath": null,
"approverHash": null,
"decidedAt": null,
"decisionAction": null,
"decisionComment": null,
"approverEmail": null,
"recipientEmail": "vendor@contoso.com"
}validationOutcome, validationResults, and currentApprovalStage are not part of this response. validationResults does appear, but only on the portal-bootstrap route (GET /api/workflow/{workflowId}, when the workflow is validation-failed) and on review-open.
Workflow States
draft | in-progress | validating | validation-failed | pending-approval |
under-review | changes-requested | escalated | approved | rejected |
sealing | complete | cancelledThese are the exact state strings used throughout the API. There is no separate isComplete/isRejected boolean pair on this API - that vocabulary belongs only to the Power Platform connector's own swagger layer.
Attachments
POST /api/workflow/{workflowId}/attachment?fieldKey=...&filename=...
GET /api/workflow/{workflowId}/attachment?blobPath=...Uploads and downloads an attachment field's file. The upload route reads the raw request body directly - there is no pre-signed SAS upload step. A 25 MB size cap and a content-type allowlist (PDF, PNG, JPEG, GIF, WebP, Word, Excel, plain text) are enforced on both the declared Content-Type and the file's own bytes; a mismatch between the two is rejected.
Auth: Upload: portal access token. Download (GET): Entra ID JWT only; a portal token is refused and uses POST /api/workflow/{workflowId}/attachment/download instead. The workflow's signed-in participants pass. An app-only token needs the Lifecycle.Reader role (otherwise 403 ROLE_MISSING). Any other signed-in person needs Lifecycle.Reader and also the document-access administrator role named by the Dispatcher's DOCUMENT_ACCESS_ADMIN_APP_ROLE setting, which standard deployments do not configure. The attachment's restriction policy, when it has one, still applies.
Governance Decision
POST /api/workflow/{workflowId}/governance-decisionRecords an approver decision on a governance workflow. Requires the workflow to be validation-failed - 409 otherwise.
Auth: Portal access token (reviewer), or the Entra ID JWT of a signed-in person. An app-only token gets 403 with code APP_CANNOT_DECIDE.
Request body:
{
"action": "approve-exception",
"comment": "Invoice exceeds PO by 1.6% - within tolerance; approved."
}action value | Behaviour |
|---|---|
approve-exception | One or more error rules failed; approver overrides with a written justification. comment must be at least 10 characters. Justification sealed into PDF. |
reject | Submission rejected. No PDF produced. comment has no minimum length. |
request-correction | Returns the workflow to validation-failed with a message for the participant. comment has no minimum length. |
The fields are action and comment - not decision and justification.
Resend Invitation
POST /api/workflow/{workflowId}/resend-inviteRe-sends the workflow invitation email to one party: the participant or the approver. Subject to portal-side rate limiting.
Auth: Entra ID JWT: a signed-in user, or an app-only token with Render.Access.
Request body:
{ "party": "approver" }| Field | Type | Required | Description |
|---|---|---|---|
party | participant | approver | Yes | Who to re-send to. The request is refused with 400 if party is missing, and with 409 if it names the party the workflow is not waiting on. |
Response: 200 { "ok": true, "sentToHash": "..." } when the invitation was queued. When the portal cannot register the link just now, the invitation is held and sent once the link registers, and the answer is 202 { "ok": true, "held": true, "sentToHash": "..." } (see When a Link Cannot Be Registered Yet).
In an escalated workflow, a Resend to the approver goes to the assigned approver, never to the escalation target. A Resend does not move the workflow's updatedAt, and the next automatic reminder waits while the person was emailed in the last 12 hours. See Reminders.
Escalate Workflow
POST /api/workflow/{workflowId}/escalateEscalates the workflow to a different approver. Requires the workflow to be under-review or pending-approval.
Auth: Entra ID JWT of a signed-in initiator. An app-only token gets 403 with code APP_CANNOT_DECIDE.
Request body:
{ "escalateTo": "director@contoso.com", "comments": "No response after 5 days" }escalateTo (an email address) is required; comments is optional. New state: escalated.
Once a stage is escalated, the assigned approver is no longer sent automatic reminders. See Reminders and escalation.
OTP - Send Code
POST /api/workflow/{workflowId}/otp/sendSends a one-time passcode to the participant's email. Only valid when requireOtp: true.
Auth: Portal access token. Rate-limited to one send per 60 seconds.
OTP - Verify Code
POST /api/workflow/{workflowId}/otp/verifyVerifies the participant's OTP code.
Auth: Portal access token
Request body: { "otp": "483921" }
OTP constraints: 6 digits, 10-minute TTL, 5-attempt lockout.
Configure Your First Workflow
See the Workflow Schema reference for step definitions, validation rules, and approval routing.
Workflow Schema Reference
Complete reference for the REFRACT Workflow YAML schema, covering step definitions, field types, validation rules, and approval routing configuration.
Guided Documents
Fill official third-party PDF forms - government forms, bank-mandated forms, compliance forms - using the same data collected through a REFRACT Workflow session. No duplicate data entry, no re-keying into a portal.