Skip to main content

The WFM model

Workforce management (WFM) answers four questions every contact center asks every day: how much work is coming in, when should each agent work, did they do what the schedule said, and how do we rebalance when reality drifts from the plan. Orbit mounts the whole surface under /api/v1/wfm as one coherent model — not a grab-bag of endpoints — and this page explains that model once so the operator workflows and API reference stay thin. WFM splits into two halves. Planning is everything that happens before the day: forecast demand, build shift templates, generate schedules, publish assignments. Adherence measurement is everything that happens during and after the day: compare what the schedule said against what agents actually did in the ACD (their aux states — online, on a call, on break, in training, logged out) and score the difference. The first half is worthless if the second never runs; the second has nothing to score without the first. The model is also two-sided by design. Managers (owner/admin) build forecasts and schedules, publish coverage, approve or deny submissions, and read the rollups. Agents self-serve: file exception requests, bid on open shifts, claim overtime or voluntary time off, and see the schedule that comes back — scoped to their own records. Every endpoint sits on one of those two sides.

1. The six WFM domains

The /wfm prefix groups its endpoints into six domains. Each domain answers one question and owns one cluster of endpoints; the domains chain together into the planning → adherence loop.

Domain 1 — Forecasts: how much work is coming in

Forecasting turns historical traffic into projected demand per channel and interval. The surface:
  • GET/POST /wfm/forecasts — create and read forecast rows (required agents per channel per interval).
  • POST /wfm/forecasts/recompute — rebuild a forecast from current history.
  • GET /wfm/forecasts/accuracy — measure forecast-vs-actual so you know how much to trust the next recompute.
  • POST /wfm/forecasts/what-if — scenario a demand change before you commit a schedule to it.
A forecast is the input every downstream domain consumes; schedules generated without one are guesswork with timestamps.

Domain 2 — Schedules and shifts: when each agent should work

A shift is a reusable time template; shift activities are the segments inside it (work, break, training); a generated schedule maps agents onto shift windows for a period. The surface:
  • GET/POST /wfm/shifts, GET/PATCH/DELETE /wfm/shifts/:id — shift template CRUD.
  • GET/POST /wfm/shifts/:shiftId/activities — the activity segments inside a shift.
  • POST /wfm/schedules/generate then POST /wfm/schedules/generate/commit — draft schedules against a forecast, then publish. The two-step split means generation is a proposal until a manager commits it.

Domain 3 — Assignments: the published roster

An assignment is the committed line that says “agent X works shift Y on date Z.” It is the roster entry adherence scoring compares reality against:
  • GET/POST /wfm/assignments, GET/PATCH/DELETE /wfm/assignments/:id — assignment CRUD.
  • POST /wfm/assignments/bulk — build a full roster in one call (typical after a schedule commit).

Domain 4 — Staffing and capacity: am I covered, what coverage costs

Given the published roster, staffing answers “do I have enough people, where, and when” — and capacity planning asks the longer-horizon question “how much coverage should the week/month carry, accounting for shrinkage” (time lost to leave, holidays, training, no-shows):
  • GET /wfm/intraday/staffing — required vs. scheduled agents per interval, today.
  • POST /wfm/staffing/optimize — the optimization pass that suggests roster changes.
  • GET /wfm/capacity/plan — capacity against shrinkage-adjusted availability.
  • GET /wfm/shrinkage, POST /wfm/shrinkage/recompute — shrinkage measurement and rebuild.
  • POST /wfm/rebalancing/recommend and POST /wfm/rebalancing/apply — propose coverage fixes, then apply them (extend/trim assignments for the drift the staffing read found).
Holiday calendars sit in this domain: the forecaster and the scheduler both treat holiday dates as reduced-capacity days, so a public-holiday week does not come out over-staffed by accident.

Domain 5 — Adherence and timekeeping: did the day go as planned

Adherence compares the published assignments against the live agent-state feed from the ACD and scores the divergence; timekeeping records the payable time on agents’ timesheets:
  • GET /wfm/adherence/intraday — today’s per-agent adherence score; GET /wfm/adherence/conformance — schedule-conformance detail; GET /wfm/adherence/team — the team rollup.
  • GET /wfm/rta/stream — the server-sent-events wallboard supervisors watch live; GET /wfm/rta/breaches — the currently-open adherence breaches without a stream.
  • GET /wfm/timesheets — timekeeping records per agent.
Adherence only reads truth when agents set their aux state (online / break / training / wrap) in the dialer or inbox — the comparison input is the state feed, not login time.

Domain 6 — Wellness, risk, and the labor market

Two maintenance surfaces keep the roster healthy and keep gaps filled between planning cycles:
  • Wellness and risk — GET /wfm/agent-wellness (burnout/strain indicators from adherence and overtime patterns) and GET /wfm/agent-flight-risk (churn-risk scoring per agent). Output feeds into coaching and capacity decisions.
  • The labor market — the gap-filling surfaces: /wfm/open-shifts (publish unowned shifts, agents bid), /wfm/overtime-offers (add hours on already-scheduled agents), /wfm/vto-offers (release hours on a slow day), /wfm/leave-policies + /wfm/leave-balances (+ /adjust) (leave types and per-agent balances), and /wfm/agent-skills (the skill tags the other domains filter coverage by).

2. Planning domain samples

Every sample below runs as written — base https://api.orbit.devotel.io/api/v1, X-API-Key on every call, and the { data, meta } envelope back (envelope shapes). The reads are scoped by wfm:read; the writes need wfm:read + wfm:write, and every endpoint named here is inventoried in the WFM API reference.

Forecasts — POST /wfm/forecasts/recompute

Recompute rebuilds the forecast rows for one channel (or all seven when you omit channel) from the tenant’s live traffic history. The call is synchronous: a 200 carries the per-channel outcome, and partial: true plus an outcome of partial_timeout or partial_error tells you which channels to re-trigger — a 25 s wall-clock budget keeps the request under the ingress timeout instead of dying mid-write.
Response — 200 (all channels completed)
What to read out of the row: channels[] is the per-channel ledger — anything not status: "completed" is a channel to re-run (a skipped_timeout row means the 25 s budget elapsed before that channel started; an error row carries its error string). upserted counts the forecast intervals written, and correlation_id is the log-grep token when a slow pass needs investigation.

Schedules — POST /wfm/schedules/generate then /wfm/schedules/generate/commit

Generation is a proposal until you commit it. /generate runs the coverage optimizer against the stored forecasts and returns the proposed assignment list plus a coverage summary — nothing is persisted on that first call. /commit persists the proposal rows (idempotently: an already-booked (agent_id, shift_id, scheduled_date) triple is skipped, never duplicated) and comes back 201 with inserted / skipped counters.
Response — 200 from /generate (trimmed to one proposal row)
Response — 201 from /commit
What to read out of the row: from /generate, the assignments array is exactly the payload you hand back to /commit after a manager reviews it — and summary.under_covered_intervals is the number that decides whether you regenerate with a wider agent pool before committing. From /commit, skipped > 0 means some dates were already booked and the insert deliberately did nothing for them; status is always scheduled so the rows feed adherence scoring unchanged.

Intraday staffing — GET /wfm/intraday/staffing

The variance read a supervisor polls during the day: required vs. scheduled agents per channel per interval against the tenant’s operating date (or a ?date= override). A variance inside the sub-agent rounding band floors to zero — a 0.001-agent residual is neither under- nor over-staffed.
Response — 200 (one interval shown)
What to read out of the row: negative net_variance with is_understaffed: true is the gap the rebalancing loop below repairs; the day-span totals (net_*_agent_minutes) are the KPI chips the dashboard renders — the required/scheduled agent values sit on the same average-headcount scale the forecast rows carry, so comparison is apples-to-apples.

3. One aggregate read — GET /wfm/summary

The WFM dashboard home assembles its top-of-page cards from one request instead of fanning out six: GET /wfm/summary returns the day’s forecast coverage, staffing totals, pending self-serve requests, open shifts, and pending bids for the tenant’s local operating date (or a date override). It resolves the same operating date as /wfm/adherence/intraday and /wfm/intraday/staffing, so the summary cards and their drill-down panels always agree on which calendar day they show.

4. Execution domain samples — the roster write + the drift repair

Two writes sit in the execution half of the loop: publishing the roster itself, and repairing that roster when intraday variance says it’s drifting. Every endpoint named here is inventoried in the WFM API reference.

Assignments — POST /wfm/assignments/bulk

The bulk-create fills one agent onto one shift over an inclusive date range in a single call — the typical follow-up right after a schedule commit, and the shape manual roster builders use directly. An optional ISO weekdays filter (1=Mon…7=Sun) narrows which days land; omit it and the shift template’s own declared weekdays apply. The insert is idempotent on the natural key, so a retry never double-books.
Response — 201 (Mon–Fri over the two-week range; one date already booked)
What to read out of the row: requested_dates is the count the range + weekday filter expanded into — verify it before you fan a bulk call across hundreds of agents, because a weekday filter hitting the wrong ISO numbers multiplies across the whole roster. inserted counts the rows that landed; every date in skipped_dates[] was already booked for that (agent, shift, date) triple and the insert deliberately did nothing — retry-proof.

Rebalance — POST /wfm/rebalancing/recommend then /wfm/rebalancing/apply

The two-step drift repair mirrors the schedule two-step: /recommend is pure compute that turns a remaining-day staffing snapshot (or the stored forecast when you omit required_agents per cell) into an ordered MOVE / ADD / RELEASE list, and /apply turns that list into floor action — short channels with a shift_by_channel mapping get their deficit posted as open shifts; channels without a mapping come back as non-writing advisories. The apply call honours dry_run: true so a supervisor (or an auto-apply caller) previews exactly what would post before anything writes.
Response — 200 from /recommend
Response — 200 from /apply (publish path taken)
What to read out of the row: on /recommend, the intervals[] ledger is where the dead-band (min_gap) already netted out sub-agent noise — only cells still actionable reached recommendations[]. On /apply, plan.publishes[] vs plan.advisories[] is the split between “will write an open shift” and “advisory only” decided by shift_by_channel, and the adherence block’s apply_ratio tracks how much of the recommended headcount actually got posted — the floor’s signal for partial apply.

5. Adherence + health reads

Three reads close the loop: the team rollup for the executive’s summary number, the live intraday preview for the supervisor’s mid-shift call, and the capacity plan for the longer-horizon “are we hiring the right shape” question. All three are inventoried in the WFM API reference.

Team rollup — GET /wfm/adherence/team

The roster-wide adherence snapshot for one operating date — the same per-agent scores the intraday read computes, aggregated tenant-wide (or queue-scoped with an optional ?queue_id=). Approved exceptions are already netted into these numbers; do not re-adjust on top.
Response — 200 (one agent row shown; agents sort worst-first)
What to read out of the row: agents_evaluated is the denominator the executive card quotes — a 0 here means the date resolution picked a day with nobody scheduled (a UTC-day mismatch the server-side resolution now handles), not an empty roster. Per agent, the raw and adjusted (excused_seconds carve-outs applied) pairs both come back so an audit can see which approved exceptions moved the number; sort is worst-first by the adjusted figure.

Coverage-drift preview — GET /wfm/adherence/intraday

The supervisor’s mid-shift read: each agent’s partial-window adherence against today’s schedule, with an is_out_of_adherence flag the wallboard renders red. The query accepts a ?threshold= override on the default out-of-adherence threshold, and an optional ?date= / ?queue_id= filter.
Response — 200 (one flagged agent shown)
What to read out of the row: is_out_of_adherence: true on a partial window is the exact signal the SSE wallboard banner pushes — poll this endpoint when you want the same list without holding a stream open. adherence_ratio / adherence_percent carry identical information (pick one), and current_state + current_state_started_at reads the live aux the dialer reported — the drift to coach on. threshold echoes back whichever override governed the flag.

Capacity plan — POST /wfm/capacity/plan

The longer-horizon read: given per-period forecast volumes, planned headcount, and shrinkage (with AHT/occupancy defaults the call fills when you omit them), the endpoint computes the FTE requirement per period against occupancy and shrinkage buffers — the “how under- or over-hired are we” answer.
Response — 200 (plan computed)
What to read out of the row: per period, status collapses the whole computation to hire (planned headcount can’t cover the shrinkage-adjusted requirement), surplus (headroom exists), or balanced; recommended_hires is the count that closes the gap this period, and the top-level peak_gap names the worst shortfall across the horizon. The defaults the call filled (max_occupancy 0.85, AHT 300 s, shrinkage 0.30) echo back, so two planners reading one payload see the same figure.

6. The request/approval loop

Every drift between plan and reality converges on the same state machine: pending → approved | denied (with cancel available while still pending). This holds across the whole surface:
  • /wfm/requests — the agent self-serve queue (time off, swap offers, shift-change requests). An agent files in pending; a manager approves or denies; the requester (or a manager) can cancel a still-pending row; an offered swap is accepted with /accept-swap.
  • /wfm/adherence-exceptions — carve-outs that excuse deviation minutes (a system outage, an all-hands meeting). Same loop: pending → approve (deviation excused) | deny (it still counts).
  • /wfm/open-shifts — publish → agents bid → manager awards one bidder.
  • /wfm/overtime-offers and /wfm/vto-offers — publish → eligible agents claim → manager awards (OT can award several claimants; VTO awards to release hours).
Two rules make the loop safe to automate around: only pending rows can be decided (a second decision returns 409), and every approve/deny/award accepts a note or reason that persists with the row — the audit trail, not an implementation detail.

6. Data roles: management vs. self-serve

Role split across the whole prefix is consistent: Non-manager callers listing exceptions, requests, or bids only ever see their own rows — the same scope rule as the rest of the tenant-isolated API. API keys carry wfm:read for reporting surfaces and wfm:read + wfm:write for anything that publishes, awards, approves, or denies.

7. How adherence scores roll up — and how exceptions carve minutes out

The scorer walks an agent’s scheduled windows for the operating day and compares each interval’s expected state from the assignment against the actual aux states the agent reported. The result is a per-agent intraday score plus a breach record for each window where reality diverged beyond the allowed threshold — breaches are what GET /wfm/rta/breaches lists and what the /wfm/rta/stream wallboard pushes live. Approved adherence exceptions rewrite that math. While an exception is approved, its time window is excluded from the deviation base — the minutes stopped being “missed schedule” the moment a manager confirmed the agent was not at fault (a dialer outage is not agent behavior). This is why the exception queue is an operations queue and not just paperwork: approving or denying it directly moves team adherence numbers, and GET /wfm/adherence-exceptions/trends exposes that movement grouped by exception type and status. Team rollups (GET /wfm/adherence/team) aggregate the same per-agent scores managers just adjusted, so the team number an executive reads already has approved carve-outs netted out — do not re-adjust on top.

8. Where the workflow guide and API reference live

This page is the concept map — plan before you operate. The procedures (file an exception, post an open shift, award a bid, close the loop) live on the Workforce-management workflows guide, and the full endpoint inventory with request/response shapes lives on the WFM API reference. Two sibling concept pages complete the picture of what the schedule adheres to: the ACD queue model (the dispatch state adherence compares against) and agent presence lifecycle (the aux-state feed adherence consumes).