# Round-robin scheduling with an API

Source: https://developer.nylas.com/docs/cookbook/scheduler/round-robin-scheduling/

A sales team of 8 reps shares one "Book a demo" link. The hard part isn't showing open times, it's deciding who gets the lead. Hand every booking to the first free person and one rep drowns while others sit idle. Round-robin scheduling spreads bookings evenly, but doing it well means checking real-time availability across every member's calendar and then assigning the slot to the right one. This recipe shows both halves: the availability call that spans a whole team, and the assignment logic that picks the next host.

## How does round robin scheduling work in a team scheduling application?

Round-robin scheduling treats a group of hosts as a single bookable resource and rotates bookings across them. When a request arrives, the system finds slots where at least one member is free, then assigns the slot to one host by a fairness rule, usually whoever was booked least recently. The booker sees one calendar for the whole team.

The mechanics break into two steps that run on every request. First, you ask for availability across all members at once so a slot counts as open if any single host can take it. Second, you pick one host from the free set and write the event to that person's calendar. The Nylas `POST /v3/calendars/availability` endpoint handles both signals in one response: it returns a `time_slots` array, each entry carrying an `emails` array listing exactly which members are free, plus a top-level `order` array ranking participants by how recently they were last booked. With 8 reps, that single call replaces 8 separate calendar lookups, returning the whole free set in one round trip.

## How do I implement round robin scheduling in Python for meeting assignment?

To implement round-robin assignment in Python, send one availability request with every team member as a participant, set `availability_method` to `max-fairness`, then read the response `order` array to pick the host. The API ranks participants by how recently they were last booked for this event type, with never-booked members first, so the first email in `order` that appears in a slot's `emails` list is your assignment.

The request below calls `POST /v3/calendars/availability` with all hosts as participants. Setting `availability_method` to `max-fairness` tells the API to rank participants so the least-recently-booked member sorts first, which is the core of even distribution. Each participant email must map to a valid Nylas grant in your application.

```bash
curl --request POST \
  --url 'https://api.us.nylas.com/v3/calendars/availability' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "start_time": 1700000000,
    "end_time": 1700086400,
    "duration_minutes": 30,
    "interval_minutes": 30,
    "participants": [
      { "email": "rep1@example.com", "calendar_ids": ["primary"] },
      { "email": "rep2@example.com", "calendar_ids": ["primary"] },
      { "email": "rep3@example.com", "calendar_ids": ["primary"] }
    ],
    "availability_rules": {
      "availability_method": "max-fairness",
      "round_robin_group_id": "demo_team"
    }
  }'
```

The response returns `order` and a list of `time_slots`. Each slot carries the `emails` of members free for that window. The Python below picks the first slot and assigns the highest-ranked free host. It iterates the `order` list, which is sorted least-recently-booked first, so the assignment naturally balances load across the team over time.

```python [roundRobinAssign-Python SDK]
from nylas import Client

nylas = Client(api_key="<NYLAS_API_KEY>")

response = nylas.calendars.get_availability(
    request_body={
        "start_time": 1700000000,
        "end_time": 1700086400,
        "duration_minutes": 30,
        "interval_minutes": 30,
        "participants": [
            {"email": "rep1@example.com", "calendar_ids": ["primary"]},
            {"email": "rep2@example.com", "calendar_ids": ["primary"]},
            {"email": "rep3@example.com", "calendar_ids": ["primary"]},
        ],
        "availability_rules": {
            "availability_method": "max-fairness",
            "round_robin_group_id": "demo_team",
        },
    }
)

availability = response.data
ranked = availability.order  # least-recently-booked host first

slot = availability.time_slots[0]
assigned_host = next(email for email in ranked if email in slot.emails)

print(f"Assign {slot.start_time}-{slot.end_time} to {assigned_host}")
```

## How do I assign each booking to the next free team member?

Assignment comes down to intersecting two lists: the `order` array, which ranks every participant by fairness, and a slot's `emails` array, which names who is actually free for that window. Walk the ranked order, take the first host that also appears in the slot, and write the event to that calendar. This guarantees both fairness and a real opening.

The pattern matters because a slot's free set changes per window. The most-fair host overall might be booked at 2:00 PM but open at 3:00 PM, so you resolve the assignment slot by slot, not once per request. The Java below mirrors the Python logic for teams on the JVM. It reads `getOrder()` and each slot's `getEmails()` from the `GetAvailabilityResponse`, then selects the first ranked host present in the target slot. With a 30-minute `duration_minutes` and a busy 8-person team, expect dozens of candidate slots in a single business day, so caching the response for the length of one booking session avoids redundant calls.

```java [roundRobinAssign-Java SDK]


public class RoundRobinAssign {
  public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();

    List<AvailabilityParticipant> participants = List.of(
        new AvailabilityParticipant.Builder("rep1@example.com").build(),
        new AvailabilityParticipant.Builder("rep2@example.com").build(),
        new AvailabilityParticipant.Builder("rep3@example.com").build()
    );

    AvailabilityRules rules = new AvailabilityRules.Builder()
        .availabilityMethod(AvailabilityMethod.MAX_FAIRNESS)
        .roundRobinGroupId("demo_team")
        .build();

    GetAvailabilityRequest request = new GetAvailabilityRequest.Builder(
        1700000000, 1700086400, participants, 30)
        .availabilityRules(rules)
        .build();

    GetAvailabilityResponse data = nylas.calendars().getAvailability(request).getData();

    List<String> ranked = data.getOrder();      // least-recently-booked first
    TimeSlot slot = data.getTimeSlots().get(0);

    String assignedHost = ranked.stream()
        .filter(email -> slot.getEmails().contains(email))
        .findFirst()
        .orElseThrow();

    System.out.println("Assign slot to " + assignedHost);
  }
}
```

## What is the difference between max-fairness and max-availability?

The `availability_method` field accepts three values: `collective`, `max-fairness`, and `max-availability`, with `max-availability` as the default. For round-robin, the choice is between the last two. Use `max-fairness` to balance load evenly across the team, and `max-availability` to surface the most open slots even if one host ends up busier.

Both methods rank participants by how recently they were last booked for the event type, least-recently-booked first, using the `key5` metadata convention to decide which events count. The difference is how strongly that ranking steers the result. `max-fairness` weights the booking history heavily so load evens out across the team over time, even when that means offering fewer total slots on a busy day. `max-availability` prioritizes open time instead, which surfaces more slots but can skew volume toward whoever has the widest availability.

| Concern | `max-fairness` | **`max-availability`** |
| --- | --- | --- |
| Optimizes for | Even load across hosts | Most open slots offered |
| Best when | You promised reps equal lead volume | You want maximum booking conversion |
| Risk | Fewer slots shown on a busy day | One host absorbs most bookings |
| `order` ranking | Least-recently-booked first, weighted for even load | Least-recently-booked first, weighted for open time |

To make either mode count the right history, tag the events you want included with metadata key `key5` set to your group identifier. The [group availability and booking best practices](/docs/v3/calendar/group-booking/#round-robin-max-fairness-groups) guide explains the `key5` convention in full.

## How do I check raw busy time before assigning?

When you need raw busy blocks rather than computed slots, call `POST /v3/grants/{grant_id}/calendars/free-busy` with the team's email addresses. It returns each member's busy `time_slots` directly from the provider, which is useful for building a custom fairness score or auditing why a host was skipped. The response carries one entry per address in the `emails` array.

This Free/Busy lookup is a lower-level companion to the availability call. The availability endpoint computes bookable slots for you; the Free/Busy endpoint hands back the underlying busy intervals so you can run your own logic, like weighting by meeting count or excluding all-day events. One request covers up to the grant's connected calendars, and the response sets an `error` field per address when a single mailbox fails, so one bad calendar never breaks the whole batch.

```bash
curl --request POST \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/calendars/free-busy' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "start_time": 1700000000,
    "end_time": 1700086400,
    "emails": ["rep1@example.com", "rep2@example.com", "rep3@example.com"]
  }'
```

Read the per-address busy blocks, score each host however your team distributes work, then write the event to the winner's calendar. For the request and response fields in detail, see the [check availability](/docs/cookbook/calendar/check-availability/) recipe.

## One tradeoff worth naming

Building round-robin on the raw Calendar API gives you full control over the fairness rule, but you own the assignment loop, the booking write, and the concurrency guard. If two requests pick the same host for the same 30-minute window within a second, you can double-book unless you add a lock. The managed Scheduler handles that race for you. So if you only need standard fair rotation and a hosted booking page, the managed flow is the faster path. Reach for this Calendar API approach when your assignment logic is genuinely custom, like routing by deal size, language, or territory. The [prevent double-booking](/docs/cookbook/scheduler/prevent-double-booking/) recipe covers the lock you'll need for the do-it-yourself path.

## What's next

- [Check availability](/docs/cookbook/calendar/check-availability/) for the Free/Busy and availability request and response fields
- [Prevent double-booking](/docs/cookbook/scheduler/prevent-double-booking/) for the concurrency lock the custom path requires
- [Round-robin email routing](/docs/cookbook/use-cases/automate/round-robin-email-routing/) to distribute inbound messages across a team the same way
- [Group availability and booking best practices](/docs/v3/calendar/group-booking/) for the `max-fairness` and `key5` metadata details