Skip to main content

Approver Delegation (Vacation Substitutions)

This guide explains how the SpendOne Platform, the AI-based platform for indirect corporate spend, handles temporary approver substitutions when an approver is out of office. A vacation substitution reroutes approval tasks from an absent approver to a designated stand-in over a fixed date window. The same mechanism covers planned vacations, sick leave and short-notice handovers, with full audit history of who approved on whose behalf.

For admins. Substitutions are managed under Profile → Out of Office for self-service or Administration → Users → Out of Office for admin-managed entries. Each substitution has a substitute (any active user other than the caller, and not themselves on vacation), a start date and an end date, both inclusive and expressed in YYYY-MM-DD. Substitutions move through four statuses: SCHEDULED (created but not yet started), ACTIVE (within the date window), COMPLETED (window ended) and CANCELED. The candidate picker returns every active user except the caller; it is deliberately not narrowed by grant, because approving is a baseline capability every user holds. Self-substitution is rejected, and a substitute who is themselves on vacation is rejected with HTTP 409. Only SCHEDULED substitutions can be edited; once the window opens, the entry is read-only and must be canceled to be undone. Activation, deactivation and notification fan-out (notification-vacation-delegation-scheduled, -activated, -ended) are scheduled jobs and run automatically. Every approval action taken by a substitute is recorded in the audit log with delegated_from set to the original approver, so reporting can always reconstruct who acted on whose behalf.

For integrators. All substitution endpoints live under /vacation-substitutions (see the API reference) and require a bearer token. The full surface:

MethodEndpointPurpose
POST/vacation-substitutionsCreate for the authenticated user
POST/vacation-substitutions/admin/{user_code}Admin creates on behalf of another user
GET/me/vacation-substitutionCurrent SCHEDULED or ACTIVE entry for caller
GET/vacation-substitutions/users/{user_code}Same, for a specified user
GET/vacation-substitutions/substitute-candidatesEligible substitutes for caller
PUT/vacation-substitutions/{code}Edit substitute or dates while SCHEDULED
DELETE/vacation-substitutions/{code}Cancel; if ACTIVE, returns delegated tasks to original approver

A typical create call:

POST /vacation-substitutions
Content-Type: application/json

{
"substitute_code": "01HXXXXXXXXXXXXXXXXXXXXXX",
"start_date": "2026-04-01",
"end_date": "2026-04-15"
}

The response contains a VacationSubstitution with code, status (SCHEDULED immediately after create), user_code, substitute_code, the date window, and timestamps. Once the start date arrives, a scheduled job activates the entry, sets status=ACTIVE, marks activated_at, and bulk-delegates the original approver's open approval tasks to the substitute by setting approver_code = substitute_code and delegated_from = original_approver_code. On end-date, a deactivation job sets status=COMPLETED, marks deactivated_at and returns any still-pending delegated tasks to the original approver.

Interaction with the rules engine​

The rules engine (/rules) emits approval plans with abstract approver references, for example cost_center_owner. Substitutions are applied after the plan is generated, when each ApprovalTask already has a concrete approver_code. The approval service calls the substitution checker for every task and, if the resolved approver is currently ACTIVE on a vacation, swaps approver_code to the substitute and stores the original in delegated_from. Two specific edge cases are handled inline:

  • Substitute is the requester. If the active substitute happens to be the buyer who created the purchase request, the task is auto-approved instead of being assigned to the buyer (no self-approval). This is recorded as auto_approval=true on the task.
  • Nested substitution. If approver A delegates to B and B is also on vacation delegating to C, the system performs a single hop only: A's tasks go to B, and B's existing or new tasks go to C. There is no recursive resolution. Configure substitute candidates to avoid known overlapping vacations; a substitute who is themselves on vacation is rejected at create and update time with a 409 substitute-on-vacation, not filtered out of the candidate list.

In-flight tasks and cancellation​

Creating a substitution does not retroactively rewrite tasks that were assigned before the window opened. Only tasks pending at activation time, plus any new tasks generated during the window, route to the substitute. Canceling an ACTIVE substitution via DELETE /vacation-substitutions/{code} calls the approval service's undelegation path, which finds pending tasks where approver_code = substitute_code and delegated_from = original_approver_code and reverts them: approver_code is restored to the original, delegated_from is cleared, and the change is written to the audit log with action_type=UNDELEGATED.

Approvals already taken by the substitute remain attributed to that substitute with delegated_from populated. They are not retroactively reassigned, since the action genuinely happened. Reporting queries that need "approved on behalf of" should join on delegated_from.

Conflict cases and error codes​

The service returns RFC 9457 problem details for the cases below. All conflicts are HTTP 409.

ConditionProblem typeSource
User already has an active or scheduled vacation…/problems/vacation-already-existsOnly one substitution per user at a time
Substitute user is also on vacation…/problems/substitute-on-vacationPrevents nested delegation at create time
User cannot substitute for themselves…/problems/self-substitutionSelf-substitution rejection
Start date is in the past…/problems/start-date-in-pastBackdating is not permitted. This one is a 400, not a 409
End date before start date400 validationInclusive date range
Update on non-SCHEDULED entry409Cancel and recreate instead

Audit trail​

Every delegated approval task carries delegated_from permanently. Audit log entries with object_type=approval and action_type=DELEGATED record the swap: old_values.approver_code is the original approver, and new_values carries the substitute plus delegated_from. Combine these with the audit log API to reconstruct the full delegation history of any plan.

For event payload shapes and full status semantics, refer to the API reference.