Workforce-management workflows
Workforce management (WFM) is the planning side of a contact center: forecasts say how many agents you need, shift templates and schedules say when each agent should be working on which activity, and the intraday loop measures whether they did. The WFM route on the dashboard — Voice → Scheduling — carries every step of that loop in tabs; the API exposes the same steps under/api/v1/wfm/*.
This guide walks a manager end-to-end: first a tour of the scheduling dashboard so you can see where each step lives, then the workflow in the order you actually run it — author the shift templates, populate the skills registry, generate a schedule, read the forecast and staffing math, tune the assignments grid, export timesheets to payroll, measure shrinkage, and finally work the day-to-day adjustments (approvals, open shifts, overtime, adherence exceptions).
Everything here is tenant-side and two-sided. Managers build schedules, publish coverage, approve or deny agent submissions, and read the rollups; agents file exception and swap requests, bid on open shifts, claim overtime or voluntary time off, and see the schedule that comes back. On the API, reads and agent self-serve writes are available to all roles (scoped to the caller’s own records when they are not a manager), while management writes — publishing offers, awarding, approving, denying — require the owner or admin role. The full endpoint inventory is on the WFM API page — this guide is the narrative; that page is the reference.
1. Dashboard tour — the scheduling tabs
Open Voice → Scheduling. If you land on a blank grid, the tour below tells you which tab you should be on.
The route also carries an Overview summary panel (operating-date resolution for the intraday panels), Leave (leave balances per policy), and Team roll-up (per-agent adherence over a chosen day) — all read views on the same data the tabs above write.
On the API every step above is scoped — every read is tenant-scoped, the payroll CSV is an
owner-/admin-only egress, and validation failures return 422 with a VALIDATION_ERROR body.
2. Prerequisites
Before any of these workflows report truthfully:- A forecast exists. The scheduler, the staffing optimizer, and capacity planning all read the stored forecast. Recheck it after history lands with
POST /api/v1/wfm/forecasts/recompute, thenGET /api/v1/wfm/forecastsreturns intervals of required agents per channel. - Agent state feeds are live. Adherence and timesheets compare the schedule against what agents actually do in the ACD: online, on a call, on break, in training, logged out. Those states come from the agent activity (aux code) feeds — if agents never set their aux state in the dialer or inbox, intraday adherence reads as zeros, shrinkage reads as near-100%, and timesheets report blanks. Fix the feed before approving any exceptions.
- API access with the right scopes. Create an API key with
wfm:readfor dashboards and reports,wfm:read+wfm:writefor anything that publishes, awards, approves, or denies.
3. Author shift templates
A shift template is the named, recurring slot agents get assigned to — for example, “Weekday morning voice support, 09:00–17:00 in Europe/Dublin.” Create one in the Shift roster tab or withPOST /api/v1/wfm/shifts:
weekdays is ISO weekday numbers 1–7 (Mon–Sun), channels is one or more of voice, chat, email, sms, whatsapp, inbox, social, and timezone must be a valid IANA zone. The required_skills list matters: schedule generation only assigns an agent to this shift when that agent holds every one of these skills, so two templates that differ only in their required_skills value pool who is eligible. Mid-night-spanning shifts (ends_at_local earlier than starts_at_local) are valid. Update with PATCH /wfm/shifts/{id}; list with GET /wfm/shifts.
4. Populate the agent-skills registry
The scheduler cannot assign an agent to a shift that names arequired_skill the agent does not hold. Skills live in the Skills tab (or PUT /api/v1/wfm/agent-skills/{agentId}/{skill}):
proficiency is an integer 1–5. Listing the registry (GET /wfm/agent-skills) is what officers use to plan cross-training; an agent’s eligibility pool for a shift is the set of agents with entries for every required_skill on that shift, so seed it before the first generation run or generation has nobody to place. Remove a skill with DELETE /wfm/agent-skills/{agentId}/{skill}.
5. Run schedule generation
Generation is the batch step that produces assignments. It reads the stored forecast, filters shifts to the ones in scope, filters agents to the skill-eligibility pool, honors the constraint set (max weekly hours, min rest hours, max consecutive days, min unpaid break), and writes assignments for the chosen range:channels restricts to those channels, shift_ids restricts to those templates, agent_ids overrides the eligibility pool entirely, and calendar_id treats the org’s holiday calendar as non-working days.
6. Read the forecast
Forecasts drive everything downstream: generation staffs to them, the rebalancer and intraday-staffing view diff scheduled agents against them, and the capacity plan extrapolates from them. The Forecast tab reads the stored forecast; regenerate it from history first:POST /wfm/staffing/optimize runs Erlang-C — either on the stored forecasts or on demand parameters you pass inline (channels with arrival_rate_per_second and expected_aht_seconds, or blended pools grouping channels a cross-trained team serves). POST /wfm/capacity/plan converts forecast volume across 1–24 labelled periods (2026-08, 2026-Q3, …) into required headcount, given productive hours per FTE, shrinkage, attrition, and occupancy target — the delta between required and planned_headcount is the hiring or attrition number for the period.
7. Work the assignments grid
Generation gets you a draft roster; the Assignments tab is where you tune it. Place an agent on a shift for a day, move them off, or fill a date-range in bulk:status of scheduled, swap_requested, swapped, time_off, or no_show; bulk fill with no weekdays filter defaults to the shift template’s own weekdays array.
8. Rebalance intraday coverage
On the day, scheduled staffing drifts away from the forecast: an agent calls in sick, a queue spike arrives. The Rebalance tab callsPOST /wfm/rebalancing/recommend with the remaining day’s per-(channel, interval) scheduled_agents, gets back a reallocation proposal (who should move from which queue to which), and applies it with POST /wfm/rebalancing/apply. The rebalancer can also share surplus across blended pools where one cross-trained group covers several channels.
9. Timesheets → payroll CSV
Time & attendance roll up per-(agent, date) worked hours in the Timesheets tab; the payroll export is the CSV feed a payroll system ingests. The export is a bulk HR egress —owner / admin only, rate-limited:
worked = regular + overtime + paid-break + unpaid-break, with a configurable daily_overtime_threshold_hours (overtime is what accrues past that per-day threshold). The CSV is the same roll-up flattened for payroll ingestion; non-UTC tenants get hours bucketed on the correct local calendar day.
10. Shrinkage report
Shrinkage is the payroll-to-productivity conversion: the portion of scheduled time that did not land as productive work (unscheduled breaks plus unscheduled absences, measured in the agent’s signed-on window). The Shrinkage tab reads the report; regenerate before you plan:11. Intraday adherence loop
On the day, two views keep supervisors ahead of coverage:- Intraday staffing —
GET /wfm/intraday/staffing?date=…diffs scheduled vs. required agents per channel for the operating date. A deficit here is what the rebalancer or an overtime offer fixes. - Real-time adherence —
GET /wfm/rta/stream(server-sent events) orGET /wfm/rta/breachesfor the current breach only.GET /wfm/adherence/intradayis the polling snapshot the wallboard uses when SSE isn’t an option.
12. Read and resolve adherence exceptions
Adherence punishes agents for deviations they had no control over: a dialer outage, an all-hands meeting, unplanned coaching. An adherence exception is a filed carve-out — while approved, those minutes stop counting as non-adherent. An agent (or a manager on their behalf) files one inpending:
exception_type is one of system_outage, emergency_meeting, unplanned_coaching, approved_absence, training, or other. A manager then lists the pending queue (filters: agent_id, status, exception_type, from, to) and decides each row:
pending rows can be approved or denied (a second decision returns 409), and only the requester — or a manager — can cancel a still-pending row with POST /adherence-exceptions/{id}/cancel. Non-manager callers only ever see their own exceptions on every list and trend call. On the dashboard, the exceptions queue sits alongside the approval queue under Voice → Scheduling.
13. Track trends for a manager dashboard
Individual decisions are only half the job — a supervisor also needs to know whethersystem_outage carve-outs are climbing or one team’s approved_absence volume is out of line. The trends endpoint returns counts and excused seconds grouped by exception type and status over a date window, which is exactly what a manager dashboard card needs:
exception_type grouping, and the pending queue from section 12 underneath. Pass agent_id to trend a single agent; non-managers calling trends get aggregates over their own rows only.
14. Post an open shift
Schedules change after publication: someone calls in sick, a forecast spike needs one more person on Saturday. An open shift publishes that uncovered slot for agents to bid on instead of a supervisor assigning it by hand:POST /open-shifts/{id}/cancel; a bidder who changed their mind withdraws with POST /open-shifts/{id}/bids/{bidId}/withdraw. On the dashboard this is the Open shifts tab under Voice → Scheduling.
15. SDK samples — the staffing-plan loop
Everything so far has been curl on the raw REST endpoints; the same steps are callable from any language that can send HTTPS — the cURL tab keeps the raw contract visible while the SDK tabs (the@devotel/sdk-node package or a plain HTTP client) do the JSON plumbing. The recipe below composes the four setup steps as one loop — recompute the forecast, create a shift template, seed one agent skill, generate and commit — and that is the correct order: generation reads the stored forecast, so recompute before you generate; the response of each step carries the id the next step needs, so the loop threads forecast_id (recompute → generate) and the (agent_id, shift_id, scheduled_date) assignment triple, or the draft schedule_id (generate → commit).
16. Handle bids
Awarding blind is how you end up re-doing the schedule — read the bid list first:17. Work overtime offers
Overtime offers use the same publish / claim / award pattern as open shifts, but in the other direction: instead of filling an unassigned shift, you put extra hours on agents already scheduled that day — a typical end-of-week coverage move when volume runs hot:POST /overtime-offers/{id}/cancel) if the spike passes before anyone is awarded; a claimant withdraws with POST /overtime-offers/{id}/claims/{claimId}/withdraw. VTO offers (/vto-offers, the Voluntary time off tab) are the exact mirror — awarding releases the claimant’s scheduled hours instead of adding them, for a slow day when you want volunteers to go home early.
18. Approval queue — time-off, swaps, preferences
Agent requests (time off, shift swaps, schedule preferences) queue in the Approval queue tab and on the API under/wfm/requests. The body is a discriminated union on type:
pending and marks the overlapping assignment as time_off (so adherence scoring does not penalize the approved absence). Shift swap starts awaiting_counterparty — the named counterparty accepts it with POST /wfm/requests/{id}/accept-swap, and only then does approve reassign the assignment atomically. Preference stays free-form and a manager acts on it manually. Reviewers decide with POST /wfm/requests/{id}/approve or /deny (both take an optional note).
19. Close the loop
An award, an approval, or a denial is not finished when the API returns200 — it is finished when the schedule is right and the audit trail is. Two closing moves:
- Publish the schedule-change back to the affected agents. An award writes the assignment (open shift) or extends it (overtime), so the roster under Voice → Scheduling → Assignments — and the agent’s own view once they reload Voice → Scheduling — is already correct; what you owe the team is the announcement. Post it in your internal Team Chat channel (or whatever channel your org uses), naming who took what so nobody builds assumptions on stale rosters: “Saturday’s open support shift went to Amira; the evening overtime went to Amira + Dev.”
- Record the exception resolution. Leave a
noteon every approve/deny call — the note travels with the row, and six months later it is the difference between an auditable decision and an unexplained line item. If the approval created follow-up work (a coaching session, a schedule correction), file it the same day while context is fresh.
20. Error shapes on the generation path
Three codes make up every generation-path failure you actually handle, all worth coding a branch for — never a blanket retry:409 SCHEDULE_CONFLICT— the schedule-check found a booking collision: an approved overlapping shift for the same agent, an assignment already covering that agent and slot, or a re-decided (approve/deny/cancel) exception row that no longer qualifies. The constraint is one booking per (agent, window) — which assignment collided decides whether to skip that draft row or cancel the existing one first. A re-decision under a different HTTP status is a bug.422 VALIDATION_ERROR— a skill-gap failure, raised when the generation draft could not place an assignment on a shift whoserequired_skillslist is not fully covered by the eligibility pool. The body names whichrequired_skillsare missing; the fix is aPUT /wfm/agent-skills/...entry for those, then re-run. Committing a draft witheligible_supply_exhausted: truereturns this shape rather than writing partial slots.422 VALIDATION_ERROR(request-shape) — a genuinely ill-shaped body (ends_at_localbeforestarts_at_localon non-midnight-spanning shifts, a proficiency outside 1–5,from_dateafterto_date). Validation failures carry field locations; the route never writes the draft before the body validates.
21. Checklist
Run this once when you stand WFM up, and again any time the numbers look wrong:- Seeded setup: at least one shift template, every active agent’s skills in the registry, a forecast computed. Generation has nothing to place until all three exist.
- Adherence gate: agents consistently set their ACD/aux state; spot-check one agent’s day — their intraday adherence timeline should match what the schedule said. If aux states are blank, fix that feed before approving any exceptions.
- Audit trail: every approve / deny / award / cancel decision carried a note or reason; exceptions are only ever decided once (
409on a re-decision tells you the queue race-checks are working). - Real-time feed: supervisors watching intraday coverage stream
GET /wfm/rta/stream(server-sent events) or read current breaches atGET /wfm/rta/breachesinstead of polling; pair it with a webhook consumer if your manager dashboard lives outside Orbit and needs push instead of a stream. - Role split: your reporting key carries
wfm:readonly; publish/award/approve actions run on a key that also haswfm:write, and onlyowner/adminaccounts hold that second key. The payroll export is scope-gated toowner/admintoo.
See also
- Workforce Management API — full endpoint reference for every call above
- Quality Management API — scorecards and the adherence panels under Quality
- Build a contact-center QA program — the quality loop that sits next to WFM; the operator-side flow that reads WFM adherence
- Webhook consumer — push real-time updates into your own manager dashboard
- Adherence exceptions in the operators console — the operator-side queue where the approve/deny calls in sections 12 and 18 land
- Error codes —
409state conflicts and422 VALIDATION_ERRORshapes