> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Workforce-management workflows

> Work the WFM loop: approve adherence exceptions, publish open shifts, award overtime offers, and close the schedule-change back to the affected agents.

# Workforce-management workflows

Workforce management (WFM) is the planning side of a contact center: forecasts, shifts, and schedules set out **when** each agent should be working on which activity; adherence scoring measures **whether** they did; and the correction workflows — adherence exceptions, open shifts, overtime and VTO offers, approval queues — handle the day-to-day drift between the two.

Everything in WFM is tenant-side and two-sided. Managers build schedules, publish coverage, approve or deny agent submissions, and read the rollups; agents file exception requests, bid on open shifts, claim overtime or voluntary time off, and see the schedule that comes back. The same split holds 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.

On the dashboard, WFM lives under **Voice → Scheduling** (open shifts, overtime, VTO, assignments, forecasts, the approval queue) with adherence reporting under **Quality**. The full endpoint inventory is on the [WFM API](/api-reference/wfm) page — this guide is the narrative; that page is the reference.

## 1. Prerequisites

Before any of these workflows report truthfully:

* **Schedules exist.** Build shift templates and assign agents to them (the **Assignments** tab under **Voice → Scheduling**, or `POST /api/v1/wfm/assignments`). Adherence is measured against scheduled activities — with no schedule there is nothing to stick to.
* **Agent state feeds are live.** Adherence compares 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, adherence reads as zeros and every exception trend is noise.
* **API access with the right scopes.** Create an API key with `wfm:read` for dashboards and reports, `wfm:read` + `wfm:write` for anything that publishes, awards, approves, or denies. All data is scoped to the calling tenant; validation failures return `422` with a `VALIDATION_ERROR` body.

## 2. Read and resolve adherence exceptions

Schedule 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 in `pending`:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "user_agent_12",
    "exception_type": "system_outage",
    "starts_at": "2026-08-24T13:00:00Z",
    "ends_at": "2026-08-24T14:30:00Z",
    "reason": "Dialer SIP trunk failover — agents could not place calls"
  }'
```

`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:

```bash theme={null}
# List pending exceptions, most recent first
curl "https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions?status=pending&from=2026-08-18&to=2026-08-24" \
  -H "X-API-Key: dv_live_sk_your_key_here"

# Approve — the deviation becomes excused from adherence scoring
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions/exc_abc123/approve \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "note": "Confirmed outage window on the status page" }'

# ...or deny — the deviation still counts against the agent
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions/exc_abc123/deny \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "note": "Schedule already covered this as a training activity" }'
```

The state machine is strict: only `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, this whole queue is the adherence panel under **Quality**.

## 3. Track trends for a manager dashboard

Individual decisions are only half the job — a supervisor also needs to know whether `system_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:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/wfm/adherence-exceptions/trends?from=2026-08-01&to=2026-08-24" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

A practical dashboard composition: three summary cards fed by the totals (exceptions this period, approved, minutes excused), one breakdown chart fed by the per-`exception_type` grouping, and the pending queue from section 2 underneath. Pass `agent_id` to trend a single agent; non-managers calling trends get aggregates over their own rows only.

## 4. 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:

```bash theme={null}
# Publish the slot (owner/admin)
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/open-shifts \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "shift_id": "shift_weekend_support", "date": "2026-08-29" }'

# Agents list what is open
curl https://api.orbit.devotel.io/api/v1/wfm/open-shifts \
  -H "X-API-Key: dv_live_sk_your_key_here"

# An agent bids — non-managers can only bid as themselves
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/open-shifts/open_abc123/bids \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "agent_id": "user_agent_12" }'

# The manager awards to one bidder
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/open-shifts/open_abc123/award \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "agent_id": "user_agent_12" }'
```

The loop from post to staffed is: publish → agents bid → manager reviews → award. If the slot fills another way, cancel it with `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**.

## 5. Handle bids

Awarding blind is how you end up re-doing the schedule — read the bid list first:

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/wfm/open-shifts/open_abc123/bids \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

A sane award policy: confirm the bidder is not already scheduled over the slot, prefer whoever is closest to their weekly hour target, then award. Awarding is what closes the loop — it writes the shift assignment for the winning agent, so their schedule (and everyone they overlap) reflects it immediately; losing bids stay on record and the open shift is no longer listable as open.

## 6. 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:

```bash theme={null}
# Publish the offer to your eligible pool (owner/admin)
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/overtime-offers \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "shift_id": "shift_evening_support", "date": "2026-08-28" }'

# An eligible agent claims it
curl -X POST https://api.orbit.devotel.io/api/v1/wfm/overtime-offers/ot_abc123/claims \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "agent_id": "user_agent_12" }'

# The manager reads the claims and awards one or more claimants
curl https://api.orbit.devotel.io/api/v1/wfm/overtime-offers/ot_abc123/claims \
  -H "X-API-Key: dv_live_sk_your_key_here"

curl -X POST https://api.orbit.devotel.io/api/v1/wfm/overtime-offers/ot_abc123/award \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "agent_ids": ["user_agent_12", "user_agent_07"] }'
```

Cancel an offer (`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.

## 7. Close the loop

An award, an approval, or a denial is not finished when the API returns `200` — it is finished when the schedule is right and the audit trail is. Two closing moves:

1. **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](/api-reference/endpoints/teamchat) 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."
2. **Record the exception resolution.** Leave a `note` on 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.

## 8. Checklist

Run this once when you stand WFM up, and again any time the numbers look wrong:

* **Adherence gate:** agents consistently set their ACD/aux state; spot-check one agent's day — their adherence timeline (`GET /wfm/adherence/intraday`) 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 (`409` on 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 at `GET /wfm/rta/breaches` instead of polling; pair it with a [webhook consumer](/guides/webhook-consumer) if your manager dashboard lives outside Orbit and needs push instead of a stream.
* **Role split:** your reporting key carries `wfm:read` only; publish/award/approve actions run on a key that also has `wfm:write`, and only `owner`/`admin` accounts hold that second key.

## See also

* [Workforce Management API](/api-reference/wfm) — full endpoint reference for every call above
* [Quality Management API](/api-reference/quality) — scorecards and the adherence panels under **Quality**
* [Build a contact-center QA program](/guides/quality-management-program) — the quality loop that sits next to WFM
* [Webhook consumer](/guides/webhook-consumer) — push real-time updates into your own manager dashboard
* [Error codes](/api-reference/error-codes) — `409` state conflicts and `422 VALIDATION_ERROR` shapes
