# Approval Flows in action The Approval Rule is the policy. The Approval Flow is the live case file for one Order: it records who has approved, who still must, and whether anyone has rejected. Where the previous pages designed governance, this page runs it. ## How a flow begins When a buyer places an Order that matches an active Approval Rule, the platform creates an [Approval Flow](/urn?urn=ctp%3Aapi%3Atype%3AApprovalFlow) automatically. The Approval Flow is the runtime record for that Order: it references the matched rules, records approvals or rejection, and exposes the role-based approver fields a buyer portal needs to decide who can act next. ```http GET /{projectKey}/as-associate/{associateId}/in-business-unit/key=atlas-apac/approval-flows/{approvalFlowId} HTTP/1.1 ``` ```json { "id": "a1b2c3d4-0000-0000-0000-000000000001", "version": 1, "status": "Pending", "order": { "typeId": "order", "id": "8f2e..." }, "approvals": [], "eligibleApprovers": [ { "associateRole": { "typeId": "associate-role", "key": "apac-manager" } }, { "associateRole": { "typeId": "associate-role", "key": "finance-director" } } ], "pendingApprovers": [ { "associateRole": { "typeId": "associate-role", "key": "apac-manager" } }, { "associateRole": { "typeId": "associate-role", "key": "finance-director" } } ], "currentTierPendingApprovers": [ { "associateRole": { "typeId": "associate-role", "key": "apac-manager" } } ] } ``` ## Who can act now: eligibility is role-based Use the approver fields on [ApprovalFlow](/urn?urn=ctp%3Aapi%3Atype%3AApprovalFlow) for two different portal decisions. `currentTierPendingApprovers` drives who should be notified now. `eligibleApprovers` helps gate approve and reject actions for Associates whose roles are still eligible in the hierarchy. Treat them as role checks, not person checks. Match the Associate's roles against `eligibleApprovers` for action eligibility. Match them against `currentTierPendingApprovers` to decide whether their approval is needed in the currently active tier. The broader `pendingApprovers` field shows roles still required across all remaining tiers. The frontend reflects these fields; the platform enforces whether the Associate can act. Because an Associate is matched on **all** their roles, someone who holds two roles spanning different open tiers can act more than once. The platform, not the frontend, enforces this; the frontend only reflects it. ## Notifying approvers The platform does not send approval notifications on its own. It exposes who to notify through `currentTierPendingApprovers`, but delivering the message is the integration's job. Wire it with the platform's eventing: - Create a [Subscription](/api/projects/subscriptions.md) for the Approval Flow [Messages](/api/projects/messages.md), such as `ApprovalFlowCreated` and `ApprovalFlowApproved`, delivered to a message queue. - In the handler, read `currentTierPendingApprovers` on the flow, resolve the Associates who hold those roles in the Business Unit, and notify them by email or a portal alert through your own channel. This keeps notification logic in your integration while the platform stays the source of truth for who is eligible to act. A buyer portal also commonly surfaces pending work directly by querying Approval Flows in the `Pending` status. ## Approving and rejecting Both actions are update actions on the Approval Flow, requiring the `manage_approval_flows` scope. An Associate approves with the `approve` action. When every role required across all tiers has approved, the platform automatically sets `status` to `Approved`. ```http POST /{projectKey}/as-associate/{associateId}/in-business-unit/key=atlas-apac/approval-flows/{approvalFlowId} HTTP/1.1 Content-Type: application/json { "version": 1, "actions": [ { "action": "approve" } ] } ``` An Associate rejects with the `reject` action, optionally giving a `reason`. ```http POST /{projectKey}/as-associate/{associateId}/in-business-unit/key=atlas-apac/approval-flows/{approvalFlowId} HTTP/1.1 Content-Type: application/json { "version": 1, "actions": [ { "action": "reject", "reason": "Over budget for this quarter" } ] } ``` A single rejection rejects the **entire** Approval Flow, and the Order receives the rejection. Rejection is not scoped to one tier or one approver: as soon as any eligible approver rejects, the flow's `status` becomes `Rejected` and no further approval can revive it. An Associate may also change a prior approval into a rejection while their tier is still open. ## Concurrency: read the version, then write Approve and reject are read-then-write operations. Notice the `version` field in each request body: you fetch the flow's current `version`, then submit it with your action. If another approver has acted in the meantime, your `version` is stale and the platform rejects the update with a `409 Concurrent modification` error. Optimistic concurrency stops two approvers from acting on the same flow at the same time and corrupting its state. Always base an approval or rejection on a freshly read flow, and treat a 409 as "someone else acted, re-read and reassess." Do not retry without checking the latest state. ## The flow state machine ```mermaid stateDiagram-v2 [*] --> Pending: Order matches an active rule Pending --> Pending: Approval recorded, tier not yet complete Pending --> Approved: All tiers fully approved Pending --> Rejected: Any eligible approver rejects Approved --> [*] Rejected --> [*] ``` ## Worked example: tracing an Atlas flow Atlas APAC has a two-tier rule: tier one requires `apac-manager`, tier two requires `finance-director`. A buyer places a qualifying AUD 60,000 order. Trace both outcomes. 1. **Flow created.** The platform creates the flow as `Pending`. `currentTierPendingApprovers` lists `apac-manager`; `finance-director` is in `pendingApprovers` but not yet current, because tier two is not open. 2. **Tier one approves.** A manager reads the flow (`version: 1`) and submits `approve` with that version. The flow advances; `currentTierPendingApprovers` now lists `finance-director`. 3. **Approval path.** The finance director reads the now-current flow (`version: 2`) and approves. All tiers are satisfied, so the platform sets `status` to `Approved` and the Order proceeds. 4. **Rejection path.** If instead the finance director submits `reject` with a `reason`, the whole flow becomes `Rejected` immediately and the Order receives the rejection, regardless of the manager's earlier approval. 5. **Concurrency.** If two managers both read `version: 1` and both submit `approve`, the first succeeds and bumps the version; the second receives `409 Concurrent modification` and must re-read the flow before deciding whether any action is still needed. ## Key takeaways - The platform creates an Approval Flow automatically when an Order matches an active rule; it starts `Pending` and references the Order. - Eligibility is role-based: gate actions on `currentTierPendingApprovers` (needed now) and `eligibleApprovers` (any open tier). - All tiers must approve for `Approved`; a single rejection sets the whole flow `Rejected`, and the Order receives the rejection. - Approve and reject are read-then-write: submit the current `version`, and treat `409 Concurrent modification` as "re-read and reassess." - The platform sends no approval notifications; subscribe to Approval Flow Messages and use `currentTierPendingApprovers` to notify the right approvers yourself. ## Related pages - [Area overview page with navigation](/learning-model-b2b-commerce.md) - [Previous page: Approver hierarchies and tiers](/learning-model-b2b-commerce/configure-approval-workflows/approver-hierarchies-and-tiers.md) - [Next page: Approval Rule inheritance](/learning-model-b2b-commerce/configure-approval-workflows/approval-rule-inheritance.md)