> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.slotflow.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.slotflow.dev/_mcp/server.

# Customer Support Scheduling

> Build an AI support agent that escalates complex issues to human callbacks.

This guide shows how to build an AI support agent that handles most tickets autonomously but escalates complex issues — billing disputes, technical troubleshooting, account recovery — to a human callback.

## The problem

Your AI support agent resolves 80% of tickets. But the other 20% need a human. Today, the agent says "a team member will reach out" and the customer waits. Sometimes for hours. Sometimes they never hear back.

With Slotflow, the agent books a specific callback time while the customer is still engaged. The customer knows exactly when they'll get help. No uncertainty, no ghosting.

## Architecture

```
Customer contacts support → AI Agent triages
  → Simple issue → Agent resolves autonomously
  → Complex issue → Agent finds available support rep
                   → Offers callback times to customer
                   → Books callback via Slotflow
                   → Webhook → Support system updated
```

## Setup

### 1. Create your support team

```javascript
const supportTeam = [
  { name: "Jordan Lee", role: "senior_support", timezone: "America/Los_Angeles" },
  { name: "Priya Patel", role: "billing_specialist", timezone: "America/New_York" },
  { name: "Tom Mueller", role: "technical_support", timezone: "Europe/Berlin" },
];

const humanIds = {};
for (const agent of supportTeam) {
  const res = await fetch("https://api.slotflow.dev/v1/humans", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SLOTFLOW_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(agent),
  });
  const human = await res.json();
  humanIds[human.role] = human.id;
}
```

### 2. Set staggered availability

Support teams often have overlapping shifts for coverage:

```javascript
// West coast: 8am-4pm PT
await setAvailability(humanIds.senior_support, {
  working_days: [1, 2, 3, 4, 5],
  work_start: "08:00",
  work_end: "16:00",
  meeting_durations: [15, 30],  // Quick callbacks and longer sessions
});

// East coast: 9am-5pm ET (overlaps with west coast)
await setAvailability(humanIds.billing_specialist, {
  working_days: [1, 2, 3, 4, 5],
  work_start: "09:00",
  work_end: "17:00",
  meeting_durations: [15, 30],
});

// Berlin: 9am-5pm CET (covers early morning US)
await setAvailability(humanIds.technical_support, {
  working_days: [1, 2, 3, 4, 5],
  work_start: "09:00",
  work_end: "17:00",
  meeting_durations: [30, 60],  // Technical issues need more time
});
```

## Agent escalation flow

The core function your agent calls when escalating a ticket:

```javascript
async function escalateToHuman({ ticketId, customerId, customerName, customerEmail, issueType, priority, summary }) {
  const API_KEY = process.env.SLOTFLOW_API_KEY;
  const BASE = "https://api.slotflow.dev/v1";
  const headers = {
    "Authorization": `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  };

  // Route to the right specialist based on issue type
  const repId = routeToSpecialist(issueType);
  const duration = priority === "high" ? 30 : 15;

  // Find available callback slots in the next 48 hours
  const now = new Date();
  const twoDaysOut = new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000);

  const slotsRes = await fetch(
    `${BASE}/humans/${repId}/slots?date_from=${formatDate(now)}&date_to=${formatDate(twoDaysOut)}&duration=${duration}`,
    { headers }
  );
  const { slots, timezone } = await slotsRes.json();

  if (slots.length === 0) {
    // Fallback: try other specialists
    return await tryOtherSpecialists(issueType, duration, headers);
  }

  // Offer the first 3 available slots to the customer
  const options = slots.slice(0, 3);

  return {
    availableSlots: options,
    timezone,
    repId,
    duration,
    metadata: {
      ticket_id: ticketId,
      customer_id: customerId,
      issue_type: issueType,
      priority,
      summary,
      escalated_at: new Date().toISOString(),
    },
  };
}

function routeToSpecialist(issueType) {
  const routing = {
    billing: process.env.BILLING_SPECIALIST_ID,
    technical: process.env.TECHNICAL_SUPPORT_ID,
    account: process.env.SENIOR_SUPPORT_ID,
    general: process.env.SENIOR_SUPPORT_ID,
  };
  return routing[issueType] || routing.general;
}
```

### Booking the selected slot

After the customer picks a time:

```javascript
async function bookCallback({ repId, selectedSlot, duration, customerName, customerEmail, metadata }) {
  const res = await fetch("https://api.slotflow.dev/v1/bookings", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SLOTFLOW_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      human_id: repId,
      starts_at: selectedSlot.starts_at,
      duration,
      attendee_name: customerName,
      attendee_email: customerEmail,
      metadata,
    }),
  });

  if (res.status === 409) {
    // Slot taken — offer fresh slots
    return { success: false, reason: "slot_taken" };
  }

  const booking = await res.json();
  return { success: true, booking };
}
```

## Webhook handler for support system

```javascript
app.post("/webhooks/slotflow", (req, res) => {
  const { event, data } = req.body;

  if (event === "booking.confirmed") {
    // Update the support ticket
    supportSystem.updateTicket(data.metadata.ticket_id, {
      status: "callback_scheduled",
      callback_time: data.starts_at,
      callback_agent: data.human_id,
      booking_id: data.id,
    });

    // Notify the support rep
    notifyRep(data.human_id, {
      customer: data.attendee_name,
      time: data.starts_at,
      issue: data.metadata.summary,
      priority: data.metadata.priority,
    });
  }

  if (event === "booking.cancelled") {
    supportSystem.updateTicket(data.metadata.ticket_id, {
      status: "callback_cancelled",
    });
  }

  res.sendStatus(200);
});
```

## Cross-timezone handling

When your support reps span multiple timezones, Slotflow handles it automatically. Each human has their own timezone — the slot engine computes availability in their local time and returns slots in UTC.

Your agent just needs to present times in the customer's preferred format:

```javascript
function formatSlotForCustomer(slot, customerTimezone) {
  const start = new Date(slot.starts_at);
  return start.toLocaleString("en-US", {
    timeZone: customerTimezone,
    weekday: "long",
    month: "short",
    day: "numeric",
    hour: "numeric",
    minute: "2-digit",
  });
  // "Monday, Mar 16, 10:00 AM"
}
```

## Cancellation flow

If a customer needs to cancel, your agent can free up the slot:

```javascript
async function cancelCallback(bookingId) {
  const res = await fetch(`https://api.slotflow.dev/v1/bookings/${bookingId}`, {
    method: "DELETE",
    headers: { "Authorization": `Bearer ${process.env.SLOTFLOW_API_KEY}` },
  });
  return await res.json();  // { id: "...", status: "cancelled" }
}
```

The slot becomes immediately available for other customers.

## Key takeaways

* **Route by issue type** — billing issues to billing specialists, technical issues to engineers
* **Use short durations** — 15-minute callbacks for quick issues, 30-minute for complex ones
* **Pass ticket context in metadata** — your webhook handler has everything it needs to update the support system
* **Offer multiple time slots** — let the customer choose from 2-3 options
* **Handle slot conflicts** — re-query slots if the customer's chosen time was already booked