Fivexer

Build a roster

A roster is a versioned plan for one date range. You can fill it manually, ask Fivexer to solve it, or pin the decisions you care about and solve the rest.

Create the planning period

ts
const roster = await fivexer.rosters.create({
  name: 'September 2026',
  periodStart: '2026-09-01',
  periodEnd: '2026-09-30',
  notify: true,
});

Omit templateIds to include every active shift template. notify: true tells workers that planning has opened so they can submit time-off requests; use false for imports and bulk scripts where a notification would be noise.

rosters.get(id) returns the expanded dated instances. Each instance has an ID such as day_ward_a@2026-09-03, its actual duration, working minutes after unpaid breaks, qualification requirements, site, and current assignees.

Assign manually

Saving assignments replaces the complete draft set and creates a new version.

ts
const detail = await fivexer.rosters.get(roster.id);

const saved = await fivexer.rosters.setAssignments(roster.id, {
  baseVersion: detail.version,
  note: 'Supervisor draft',
  assignments: [
    { employeeId: 'worker_anna', shiftInstanceId: 'day_ward_a@2026-09-03' },
    { employeeId: 'worker_ben', shiftInstanceId: 'night_ward_a@2026-09-03' },
  ],
});

Always send baseVersion from the roster you read. If another editor saved first, the API returns 409 roster_version_conflict instead of overwriting their work. Re-read, reconcile, and save again.

The response includes a fresh lint result. A save may preserve a non-compliant draft; publishing is where you deliberately decide whether the plan is ready.

Ask who can take a shift

ts
const candidates = await fivexer.rosters.candidates(roster.id, {
  shiftInstanceId: 'night_ward_a@2026-09-03',
});

Candidates include eligible and blocked people. Each row carries rank, rationale, marginal cost, fairness debt, travel context, and blockers. Use rosters.explain when you need every rule verdict for one person and one shift.

Solve automatically

ts
const started = await fivexer.rosters.solve(roster.id, {
  timeBudgetMs: 10_000,
});

let job = (await fivexer.rosters.solveStatus(roster.id)).job;
while (job?.status === 'queued' || job?.status === 'running') {
  await new Promise((resolve) => setTimeout(resolve, 500));
  job = (await fivexer.rosters.solveStatus(roster.id)).job;
}

Solving runs asynchronously and creates a result version. It does not publish. The time budget controls how long improvement may continue, not whether constraints apply: hard requirements cannot be traded for a cheaper or fairer plan.

If no complete plan is possible, call rosters.diagnose(id). It reports structural causes such as zero eligible workers, missing qualifications, or capacity shortfalls. A partial answer is evidence about the gap, not a successful roster.

Compare versions

rosters.versions(id) lists manual, solved, repaired, and published versions with their assignment count and lint summary. That makes experimentation reversible: compare a manager’s draft with a solver result, then publish the version you actually approve.

Before publishing, understand the working-time rules and run validation and preflight.

Was this page helpful?