# Reassigning (/docs/scheduling/reassigning)

A booking can go to another host at any point before it starts, and keeps its id, its time and its attendee. The old
host's calendar event goes, the new host's comes, and whoever's leaving and joining are told.

## See who could take it [#see-who-could-take-it]

Candidates are the environment's other active hosts, event type members first, in the order a pool would pick them:

```ts twoslash
import { Coss } from "coss";
const coss = new Coss();
declare const bookingId: string;
// ---cut---
const { data } = await coss.scheduling.bookings.reassignCandidates(bookingId);
```

Each says whether they're `available`, and if not, the `reason`. `load` is their bookings in the week from this one, and
`meeting_provider_missing` warns that they haven't connected what this booking's meeting needs.

## Give it to someone [#give-it-to-someone]

```ts twoslash
import { Coss } from "coss";
const coss = new Coss();
declare const bookingId: string;
// ---cut---
await coss.scheduling.bookings.reassign(bookingId, {
  changes: [{ to_host: "doctor_518" }],
  reason_code: "out_of_office",
});
```

Or let coss.com pick, as a pool does: the fairest who's free. `auto` keeps to what the booking was
[routed with](/docs/scheduling/routing), so a dermatology patient doesn't end up with a cardiologist. `preferred_hosts`
are tried first:

```ts twoslash
import { Coss } from "coss";
const coss = new Coss();
declare const bookingId: string;
// ---cut---
await coss.scheduling.bookings.reassign(bookingId, {
  changes: [{ auto: true, preferred_hosts: ["doctor_518"] }],
  reason_code: "workload",
});
```

A booking made before routing was remembered can't say what it was routed with, so a pool's refuses `auto`: name a host.

## What moves [#what-moves]

| Booking | What happens |
| --- | --- |
| One-on-one or pool | The host changes. A pool's fairness count follows the booking. |
| Collective | Each required host can be replaced. A booking with several hosts needs `from_host`, and `changes` can replace several at once, all or none. |
| Group session | The whole session moves, from any of its seats. |
| Hold or request | It keeps what's left of its time, never more. The new host is who approves a request. |
| Already started | It changes for the record: no calendars, no claim, and nobody's told. |

A booking whose event type has `reassignment_enabled: false` stays with its host. A series is booked one occurrence at a
time, so each moves on its own.

## When someone isn't free [#when-someone-isnt-free]

Reassigning checks their hours, calendar, limits and time off, and says why not. You can go ahead anyway, with a
`note` saying why:

```ts twoslash
import { Coss } from "coss";
const coss = new Coss();
declare const bookingId: string;
// ---cut---
await coss.scheduling.bookings.reassign(bookingId, {
  changes: [{ to_host: "doctor_518" }],
  reason_code: "host_unavailable",
  overrides: ["outside_availability"],
  note: "Dr. Raman offered to come in early.",
});
```

`any_host` allows someone who doesn't take this event type, and needs no note. Someone who already has something at
this time can never be overridden: nothing here can double-book a host.

## Who's told [#whos-told]

The host joining gets the invitation, the host leaving gets a cancellation, and the attendee gets an update with the same
calendar invitation. A co-host who isn't changing hears nothing.

Pass `notify: { attendee: false }` for one booking, or turn `reassigned` off in the
[notification settings](/docs/scheduling/notifications). Attendee emails don't name the new host unless you set
`name_host_in_attendee_emails`.

## Meetings [#meetings]

A Google Meet, Teams or Zoom link belongs to the host whose account made it. If they leave, someone still on the booking
gets a new one, and the attendee hears when it's ready. If they stay, the link does too.

## Reasons [#reasons]

`reason_code` and `note` stay in the booking's history. They're never in webhooks, logs or emails, and the note is
encrypted and goes when the attendee's details do.

## Hosts giving away their own [#hosts-giving-away-their-own]

Hosts can't reassign their own bookings until you let them:

```ts twoslash
import { Coss } from "coss";
const coss = new Coss();
// ---cut---
await coss.scheduling.settings.update({ host_reassignment: "eligible" });
```

They then get a button on each booking on their page, listing who's free and takes that event type, by name. They don't
see why anyone else isn't listed, and can't go past anyone's availability. [Team leads](/docs/scheduling/embedding) can.

## Many at once [#many-at-once]

When someone's away, leaving or has too much on, a run moves up to 5,000 bookings in the background. See who each would
go to first:

```ts twoslash
import { Coss } from "coss";
const coss = new Coss();
// ---cut---
const { data: preview } = await coss.scheduling.reassignments.preview({
  selection: { host: "doctor_482", start_before: "2027-08-14T00:00:00.000Z" },
  strategy: { preferred_hosts: ["doctor_518"] },
});
```

Each item says who it would go to, or why it would stay. The preview works down them in order, so two can't both be
promised the same free hour, and covers the first 500. Change any of it and start the run with what you've decided:

```ts twoslash
import { Coss } from "coss";
const coss = new Coss();
declare const bookingId: string;
// ---cut---
const { data: run } = await coss.scheduling.reassignments.create(
  {
    selection: {},
    strategy: { assignments: [{ booking: bookingId, to_host: "doctor_518" }] },
    reason_code: "out_of_office",
  },
  { idempotencyKey: "cover-priya-august" },
);
```

Or skip the preview and say how to choose. A `selection` needs a `host`, an `out_of_office` entry, `booking_ids` or a
`series`, and can be narrowed with `start_after`, `start_before`, `event_types` and `external_conflict`. A `strategy` is
one of:

| Strategy | What it does |
|---|---|
| `{ to_host }` | Gives them all to one host |
| `{ preferred_hosts }` | Tries these in order, then the fairest who's free |
| `{ preferred_hosts, otherwise: "leave" }` | Tries these in order, and leaves the rest where they are |
| `{ assignments }` | Says who each goes to |

The run decides again for each booking, since things change. **Nothing's ever cancelled**: a booking nobody can take
stays where it is, and the run says so. Each booking is its own transaction, so one that can't move never holds up the
rest, and a series stays on one host where it can. A group session counts once.

```ts twoslash
import { Coss } from "coss";
const coss = new Coss();
declare const runId: string;
// ---cut---
const { data: progress } = await coss.scheduling.reassignments.get(runId);
const { data: items } = await coss.scheduling.reassignments.items(runId);
await coss.scheduling.reassignments.cancel(runId);
await coss.scheduling.reassignments.undo(runId);
```

`counts` says how it's going, and each item what happened: `reassigned`, `skipped_changed` (it moved on since),
`no_candidate`, `failed` or `cancelled`. Cancelling stops what hasn't happened yet. Undoing gives back what moved, as a
new run: a booking that's moved on since stays where it went, and a former host who's no longer free keeps theirs from
going back, unless you pass `overrides`.

By default each host gets one email for the run, saying how many came and went, and attendees hear per booking.
`notify: { hosts: "each" }` sends hosts one per booking, `"none"` none, and `attendees: false` leaves attendees out.

To move a host's bookings when they go away, say so on [the entry](/docs/scheduling/out-of-office#move-what-its-in-the-way-of).

## Webhooks [#webhooks]

`scheduling.booking.reassigned` carries `host_ids` and `previous_host_ids`, and `reassignment_id` when a run moved it.
`scheduling.reassignment.completed` says a run's finished or was cancelled, with its `counts`. See
[Webhooks](/docs/webhooks).
