# Time off & cover

Time off and urgent cover are related but different workflows.

- **Time off** records a request and a decision, normally before publication.
- **Cover** handles an absence or no-show against a published roster.

## Decide time off with impact visible

```
tsconst requests = await fivexer.timeOff.list({ status: 'pending' });
const impact = await fivexer.timeOff.impact(requestId);

if (impact.affected.every((roster) => roster.droppedShifts === 0)) {
  await fivexer.timeOff.approve(requestId);
} else {
  await fivexer.timeOff.deny(requestId, { note: 'Discuss alternative dates' });
}
```

Approved requests constrain future drafts and solves. Pending requests appear in publish preflight. Denied and cancelled requests remain part of the decision record but do not block scheduling.

## Report an absence

```
tsconst cover = await fivexer.rosters.reportCover(rosterId, {
  workerId: 'worker_anna',
  reason: 'sick',
  fromDate: '2026-09-03',
  toDate: '2026-09-04',
  note: 'Reported before day shift',
});
```

For a no-show on one occurrence, send `shiftInstanceId`. Otherwise, every assignment for that worker in the date range becomes an opening. The original roster and the absence record remain distinguishable.

## Find cover

```
tsconst result = await fivexer.rosters.coverCandidates(rosterId, cover.id);
```

Candidates are grouped by operationally useful tiers:

- `home`: based at the shift’s own site;
- `nearby`: a known journey away;
- `unknown`: no travel time was supplied;
- `unplaced`: no useful origin is known.

Within each tier, candidates include eligibility, rationale, marginal cost, fairness debt, origin site, travel time, and blockers. `unknown` never means zero travel; it means the system refuses to invent a journey.

## Fill directly or make an offer

Use direct fill when the supervisor has already confirmed the replacement:

```
tsawait fivexer.rosters.fillCover(rosterId, cover.id, openingId, {
  workerId: 'worker_ben',
});
```

Use an offer when the worker must choose:

```
tsawait fivexer.rosters.offerCover(rosterId, cover.id, openingId, {
  workerIds: ['worker_ben', 'worker_cora'],
  expiresInMinutes: 30,
});
```

The offer snapshots its travel context so later site edits do not rewrite what the worker was asked to accept. Workers can accept or decline through their portal. An accepted offer fills the opening; a decline leaves it open for the next candidate.

Drop an opening only when it no longer needs cover. Cancel the cover request when the absence itself was withdrawn. These are different actions and preserve different histories.

## Keep the change small

The cover workflow ranks a replacement against the same constraint set used to build and validate the roster. Untouched shifts stay untouched. This avoids turning one sick call into a completely different month for everybody else.

For location-aware candidate ordering, configure [multi-site planning](/docs/rostering/multi-site/).
