Calendly-style booking primitives: define event types, expose available time slots, and accept bookings with automatic conflict detection.
Schedule API
The SimplySchedule API provides Calendly-style booking primitives: define event types with weekly availability, expose free time slots to visitors, and accept bookings via a public endpoint with automatic conflict detection.
Concepts
- Event type — a bookable offering (e.g. "30 min discovery call") with a duration, optional buffers, and weekly availability windows
- Availability window — a recurring weekly slot (day of week + start/end time) when bookings can be created against an event type
- Booking — a single reservation against an event type with a confirmed/cancelled/completed/no_show status
Authentication
Reading public availability and creating bookings do not require an API key. Listing bookings, creating or modifying event types, and updating bookings require a project-scoped API key in the x-api-key header.
Create an Event Type
POST /api/v1/schedule/event-types
Creates a bookable event type and (optionally) its weekly availability windows in a single call.
const res = await fetch('https://www.simplystack.dev/api/v1/schedule/event-types', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_PROJECT_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
slug: 'discovery-call',
name: '30 min Discovery Call',
description: 'Quick intro chat to see if we are a fit.',
duration_min: 30,
buffer_before_min: 5,
buffer_after_min: 5,
timezone: 'UTC',
location_type: 'video',
availability: [
{ day_of_week: 1, start_time: '09:00', end_time: '17:00' },
{ day_of_week: 2, start_time: '09:00', end_time: '17:00' },
{ day_of_week: 3, start_time: '09:00', end_time: '17:00' },
{ day_of_week: 4, start_time: '09:00', end_time: '17:00' },
{ day_of_week: 5, start_time: '09:00', end_time: '13:00' },
],
}),
});
const { data } = await res.json();
console.log(data.id);Request Body
- slug (string, required) — lowercase / hyphenated, unique per project, max 80 chars
- name (string, required) — display name
- duration_min (integer, required) — 1 to 1440
- buffer_before_min, buffer_after_min (integer, default 0) — minutes blocked around each booking
- timezone (string, default "UTC") — IANA tz name stored with the event type. NOTE: the current availability and booking endpoints interpret all times in UTC; timezone-aware slot computation is planned for a future release.
- location_type (string, default "video") — video | phone | in_person | custom
- location_value (string, optional) — link, address, or note
- active (boolean, default true) — inactive event types reject new bookings
- availability (array, optional) — weekly windows: { day_of_week (0=Sun..6=Sat), start_time, end_time }
List, Read, Update, Delete Event Types
- GET /api/v1/schedule/event-types — list this project's event types with their availability
- GET /api/v1/schedule/event-types/:id — single event type
- PUT /api/v1/schedule/event-types/:id — update fields; if you pass an `availability` array, all existing windows are replaced
- DELETE /api/v1/schedule/event-types/:id — delete event type and all related bookings (cascade)
Get Available Slots (Public)
GET /api/v1/schedule/availability/:eventTypeId
Returns free booking slots over a date range. No API key required for active event types.
const res = await fetch(
`https://www.simplystack.dev/api/v1/schedule/availability/${eventTypeId}` +
`?date=2026-05-01&days=14`
);
const { data } = await res.json();
// data.days = [{ date: '2026-05-04', slots: ['2026-05-04T09:00:00Z', ...] }]Query Parameters
- date (YYYY-MM-DD, default today) — start of the range
- days (number, default 14, max 60) — how many days to check
Each slot start is returned in UTC ISO-8601 format. The slot duration equals the event type's duration_min, and slots that conflict with existing confirmed bookings (including buffers) are excluded automatically.
Create a Booking (Public)
POST /api/v1/schedule/bookings
Books a slot. The server validates that the requested start time falls inside an availability window and does not collide with another confirmed booking (with buffers applied).
const res = await fetch('https://www.simplystack.dev/api/v1/schedule/bookings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event_type_id: 'EVENT_TYPE_ID',
scheduled_at: '2026-05-04T14:00:00Z',
attendee_name: 'Jane Doe',
attendee_email: 'jane@example.com',
attendee_notes: 'Looking forward to chatting!',
}),
});
const { data } = await res.json();
console.log(data.status); // "confirmed"Request Body
- event_type_id (uuid, required)
- scheduled_at (ISO-8601, required) — must be in the future
- attendee_name (string, required) — max 200 chars
- attendee_email (string, required) — must be a valid email
- attendee_notes (string, optional)
- metadata (object, optional)
Errors
- 400 — invalid input or attempting to book in the past
- 403 — event type is inactive
- 404 — event type not found
- 409 — slot is outside availability or already booked
Manage Bookings
- GET /api/v1/schedule/bookings — list bookings for the project; filter with event_type_id, status, from, to, limit
- PATCH /api/v1/schedule/bookings/:id — update status (confirmed | cancelled | completed | no_show), notes, or cancellation reason
- DELETE /api/v1/schedule/bookings/:id — hard delete a booking
Example: Embedding a Booking Page
A minimal Calendly-style flow: fetch slots, render a date/time picker, then post the booking.
// 1) Load slots for the next two weeks
const eventTypeId = 'EVENT_TYPE_ID';
const slotsRes = await fetch(
`https://www.simplystack.dev/api/v1/schedule/availability/${eventTypeId}?days=14`
);
const { data: avail } = await slotsRes.json();
// 2) When the user picks a slot, submit the booking
async function book(slotIso, name, email) {
const res = await fetch(
'https://www.simplystack.dev/api/v1/schedule/bookings',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event_type_id: eventTypeId,
scheduled_at: slotIso,
attendee_name: name,
attendee_email: email,
}),
}
);
if (res.status === 409) throw new Error('Slot just got taken');
return (await res.json()).data;
}