34 endpoints

API Reference

REST API for managing profiles, events, bookings, availability, calendar intelligence, and scheduling requests.

Quick Start

https://skdul.ai/api/v1

Generate an API key from Dashboard → MCP Server → API Keys. Keys start with sk_live_.

# 1. Find available slots for an event type
curl "https://skdul.ai/api/v1/availability/slots?eventTypeId=<id>&startDate=2026-06-01&endDate=2026-06-07" \
  -H "Authorization: Bearer sk_live_<your_key>"

# 2. Book one of the returned slots
curl -X POST https://skdul.ai/api/v1/bookings \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "eventTypeId": "<id>",
    "startTime": "2026-06-02T14:00:00Z",
    "endTime": "2026-06-02T14:30:00Z",
    "guestName": "John Smith",
    "guestEmail": "john@example.com"
  }'

# → 201 { "bookingId": "...", "eventTitle": "30 Minute Meeting" }
# → 409 booking_conflict  if the slot was just taken — fetch fresh slots and retry

Authentication

All /api/v1/* endpoints require an API key passed in the Authorization header as a Bearer token.

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx

Keep keys secret. Do not commit them to version control, embed them in client-side code, or share them in public repositories. Rotate any key you believe has been exposed from Dashboard → MCP Server → API Keys.

HTTP/1.1 401 Unauthorized

{
  "error": {
    "type": "authentication_error",
    "code": "api_key_missing",
    "message": "No API key provided. Include your key as 'Authorization: Bearer sk_live_...'.",
    "doc_url": "https://skdul.ai/docs/api#error-handling"
  }
}

Error Handling

All errors return a structured JSON object — never a flat string. The error.type field classifies the failure category; error.code gives the machine-readable cause to switch on in client code. When a specific parameter caused the error, it is named in error.param.

{
  "error": {
    "type": "invalid_request_error",   // error category
    "code": "missing_required_param",  // machine-readable cause
    "message": "Missing required param: 'guestEmail'.",
    "param": "guestEmail",             // present when a param is at fault
    "doc_url": "https://skdul.ai/docs/api#error-handling"
  }
}
HTTP/1.1 404 Not Found

{
  "error": {
    "type": "not_found_error",
    "code": "resource_not_found",
    "message": "No such booking: '7c8d9e0f-1a2b-3456-cdef-012345678901'.",
    "doc_url": "https://skdul.ai/docs/api#error-handling"
  }
}
HTTP/1.1 409 Conflict

{
  "error": {
    "type": "conflict_error",
    "code": "slot_unavailable",
    "message": "The requested time is outside the host's available hours. Use GET /api/v1/availability/slots to find open times.",
    "doc_url": "https://skdul.ai/docs/api#error-handling"
  }
}

Error fields

FieldAlwaysDescription
error.typeYesError category: authentication_error, authorization_error, invalid_request_error, not_found_error, conflict_error, api_error.
error.codeYesMachine-readable cause. Switch on this in client code — see the table below for all codes.
error.messageYesHuman-readable explanation. Includes the offending resource ID on 404s and the resolution path on 409s.
error.paramNoThe request parameter that caused the error, when applicable.
error.doc_urlYesPermalink to this errors reference.

HTTP status codes

CodeMeaningerror.codeWhen it occurs
400Bad Request
missing_required_paraminvalid_param_valueinvalid_action
A required parameter is missing or a parameter value is invalid. The error.param field names the offending field.
401Unauthorized
api_key_missingapi_key_invalid
Missing or invalid API key. Verify the Authorization header is present and the key is active in the dashboard.
403Forbidden
access_denied
The API key is valid but the authenticated user does not own the requested resource.
404Not Found
resource_not_found
The resource ID does not exist or belongs to another account. The message includes the exact ID that was not found.
409 (booking_conflict)Conflict
booking_conflict
Another booking was confirmed at this time slot between your request and the server. Fetch fresh slots and retry with a new time.
409 (slot_unavailable)Conflict
slot_unavailable
The requested time falls outside the host's configured working hours. Use GET /api/v1/availability/slots to find valid times.
429Too Many Requests
rate_limit_error
Request rate exceeded. Back off and retry after the interval specified in the Retry-After header.
500Internal Server Error
internal_error
An unexpected server error. Retry with exponential backoff. If the problem persists, contact support.

All error codes

Every error.code the API can return, with its type, HTTP status, an example message, and the condition that triggers it.

error.codetypeHTTPExample messageWhen it fires
api_key_missingauthentication_error401No API key provided. Include your key as 'Authorization: Bearer sk_live_...'.The Authorization header is absent or does not start with 'Bearer sk_live_'.
api_key_invalidauthentication_error401Invalid API key. Check your key in the dashboard under MCP Server → API Keys.The key is not found in the database, or has been revoked.
access_deniedauthorization_error403You do not have permission to access this resource.The API key is valid but the authenticated user does not own the requested resource.
missing_required_paraminvalid_request_error400Missing required param: 'eventTypeId'.A required body or query parameter is absent. The error.param field names the missing field.
invalid_param_valueinvalid_request_error400Slug must contain only lowercase letters, numbers, and hyphens (max 60 chars).A parameter is present but fails validation (wrong format, out of range, invalid enum value). The error.param field names the offending field.
invalid_actioninvalid_request_error400'action' must be 'accept' or 'reject'.An action or enum field contains an unrecognised value. The error.param field names the field.
resource_not_foundnot_found_error404No such booking: '7c8d9e0f-1a2b-3456-cdef-012345678901'.The resource ID does not exist or belongs to another account. The message always includes the exact ID that was not found.
booking_conflictconflict_error409This time slot is already booked.Another booking was confirmed at the same slot between your request and the server. Fetch fresh slots via GET /api/v1/availability/slots and retry with a new time.
slot_unavailableconflict_error409The requested time is outside the host's available hours. Use GET /api/v1/availability/slots to find open times.The requested time falls outside the host's configured working hours. No retry at the same time will succeed — fetch available slots first.
internal_errorapi_error500An unexpected error occurred. If this problem persists, contact support.An unhandled server exception occurred. Retry with exponential backoff. If the problem persists, contact support with the request details.

Rate Limits

The API does not enforce hard rate limits. As a courtesy, keep requests under 60 per minute per API key. If you exceed this threshold you may receive a 429 response. The Retry-After header on 429 responses tells you how many seconds to wait. Use exponential backoff starting at 1 second for any retry logic.

Pagination

List endpoints accept a limit query parameter (max 100, default 20) and return a flat JSON array — not wrapped in a data envelope. An empty result set returns []. If the number of results equals your limit, there may be more — narrow results further using filters like status or guestEmail. Cursor-based pagination with a has_more flag is on the roadmap.

Versioning

The API is versioned via the URL path — all endpoints documented here are under /api/v1/. We follow a conservative breaking-change policy: additive changes (new optional fields, new optional parameters, new endpoints) are shipped without a version bump. Removals, type changes, or renamed parameters are breaking changes and will be announced with at least 30 days notice before being released under /api/v2/.

Idempotency

Booking creation is implicitly idempotent — attempting to book a slot that is already taken returns 409 Conflict rather than creating a duplicate. For other POST endpoints, safe retries are your responsibility. Explicit Idempotency-Key header support is on the roadmap.

Object Models

Core objects returned across the API. Field types, nullable flags, and enum values shown here apply everywhere the object appears — you do not need to re-derive them from individual endpoint responses.

The Booking object

Represents a confirmed meeting between a host and a guest. Created via POST /api/v1/bookings or automatically when a scheduling request is accepted.

FieldTypeDescription
idstring (uuid)Unique booking identifier.
event_type_idstring (uuid)The event type this booking belongs to.
host_idstring (uuid)User ID of the host.
start_timestring (ISO 8601)Meeting start time in UTC.
end_timestring (ISO 8601)Meeting end time in UTC.
duration_minutesnumberDuration of the meeting in minutes.
guest_namestringGuest's full name.
guest_emailstringGuest's email address.
guest_timezonenullablestringGuest's IANA timezone at time of booking.
guest_notesnullablestringOptional notes submitted by the guest.
statusstringCurrent booking status.
One of: confirmed, cancelled_by_host, cancelled_by_guest, rescheduled, completed, no_show
location_typestringMeeting format.
One of: video, phone, in_person, custom
meeting_urlnullablestringVideo conference URL when location_type is video.
sourcestringHow the booking was created.
One of: mcp, api, booking_page, scheduling_request
reschedule_countnumberNumber of times this booking has been rescheduled.
original_booking_idnullablestring (uuid)ID of the booking this was rescheduled from, if applicable.
created_atstring (ISO 8601)When the booking was created.
{
  "id": "7c8d9e0f-1a2b-3456-cdef-012345678901",
  "event_type_id": "3b4c5d6e-7f8a-9012-bcde-f01234567890",
  "host_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "start_time": "2026-03-15T14:00:00Z",
  "end_time": "2026-03-15T14:30:00Z",
  "duration_minutes": 30,
  "guest_name": "John Smith",
  "guest_email": "john@example.com",
  "guest_timezone": "America/Chicago",
  "guest_notes": "Looking forward to it",
  "status": "confirmed",
  "location_type": "video",
  "meeting_url": "https://zoom.us/j/123456789",
  "source": "booking_page",
  "reschedule_count": 0,
  "original_booking_id": null,
  "created_at": "2026-03-10T09:42:00Z"
}

The EventType object

A meeting template that defines duration, location, buffer times, and booking constraints. Each event type has a public booking page at /{username}/{slug}.

FieldTypeDescription
idstring (uuid)Unique event type identifier.
user_idstring (uuid)Owner's user ID.
titlestringDisplay name shown on the booking page.
slugstringURL-safe identifier. Booking page is at /{username}/{slug}.
duration_minutesnumberDefault meeting duration in minutes.
descriptionnullablestringDescription shown to guests before booking.
location_typestringMeeting format.
One of: video, phone, in_person, custom
buffer_beforenumberMinutes blocked before each meeting.
buffer_afternumberMinutes blocked after each meeting.
minimum_noticenumberMinimum advance notice required, in minutes.
max_per_daynullablenumberMaximum bookings per day. null = unlimited.
colornullablestringDisplay color hex code.
is_activebooleanWhether this event type is open for new bookings.
is_secretbooleanIf true, the event type is not listed on the public profile page.
scheduling_typestringMeeting format type.
One of: one_on_one, group
created_atstring (ISO 8601)When the event type was created.
{
  "id": "3b4c5d6e-7f8a-9012-bcde-f01234567890",
  "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "title": "30 Minute Meeting",
  "slug": "30min",
  "duration_minutes": 30,
  "description": "Quick chat to discuss your project",
  "location_type": "video",
  "buffer_before": 0,
  "buffer_after": 5,
  "minimum_notice": 60,
  "max_per_day": null,
  "color": "#E8634A",
  "is_active": true,
  "is_secret": false,
  "scheduling_type": "one_on_one",
  "created_at": "2026-01-15T10:00:00Z"
}

The Profile object

The authenticated user's account and display settings. Controls how the booking page looks and how availability is calculated.

FieldTypeDescription
idstring (uuid)Unique user identifier.
usernamestringURL-safe username used in booking page URLs.
full_namenullablestringDisplay name shown on booking pages.
emailstringAccount email address.
timezonestringIANA timezone identifier used for availability calculations.
time_formatstringClock format.
One of: 12h, 24h
brand_colornullablestringHex color code used on the booking page.
bionullablestringShort bio visible on the public booking page.
week_startstringFirst day of the week on calendar views.
One of: sunday, monday
avatar_urlnullablestringURL of the profile avatar image.
planstringCurrent billing plan.
One of: free, pro
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "username": "janedoe",
  "full_name": "Jane Doe",
  "email": "jane@example.com",
  "timezone": "America/New_York",
  "time_format": "12h",
  "brand_color": "#E8634A",
  "bio": "Product designer based in NYC",
  "week_start": "sunday",
  "avatar_url": "https://skdul.ai/avatars/janedoe.jpg",
  "plan": "pro"
}

The SchedulingRequest object

An async meeting negotiation between two skdul users. The engine finds mutual availability and proposes a slot — the recipient accepts or rejects it. Accepting automatically creates a confirmed Booking.

FieldTypeDescription
idstring (uuid)Scheduling request identifier.
initiator_user_idstring (uuid)User ID of the person who sent the request.
target_usernamestringUsername of the request recipient.
titlestringMeeting title proposed by the initiator.
duration_minutesnumberProposed meeting duration in minutes.
statusstringCurrent request status.
One of: pending, accepted, rejected, cancelled, expired
matched_slot_startnullablestring (ISO 8601)Start of the mutually available slot found by the engine.
matched_slot_endnullablestring (ISO 8601)End of the matched slot.
notesnullablestringOptional context message from the initiator.
created_atstring (ISO 8601)When the request was created.
expires_atnullablestring (ISO 8601)When the request auto-expires if not responded to.
{
  "id": "9e0f1a2b-3c4d-5678-defa-123456789012",
  "initiator_user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "target_username": "janedoe",
  "title": "Product feedback session",
  "duration_minutes": 30,
  "status": "pending",
  "matched_slot_start": "2026-03-15T15:00:00Z",
  "matched_slot_end": "2026-03-15T15:30:00Z",
  "notes": "Would love to get your thoughts on the new feature",
  "created_at": "2026-03-10T10:00:00Z",
  "expires_at": "2026-03-17T10:00:00Z"
}

Profile

2 endpoints

GET/api/v1/profile

Retrieves the authenticated user's full profile. The response includes display settings, branding configuration, and the current plan. Use this to populate scheduling pages or sync profile data to your own systems.

Returns Returns a Profile object.

Response Schema

FieldTypeDescription
idstring (uuid)Unique user identifier.
usernamestringURL-safe username used in booking page URLs.
full_namenullablestringDisplay name shown on booking pages.
emailstringAccount email address.
timezonestringIANA timezone identifier (e.g. America/New_York).
time_formatstringClock format.
One of: 12h, 24h
brand_colornullablestringHex color code used on the booking page.
bionullablestringShort bio visible on the public booking page.
week_startstringFirst day of the week on calendar views.
One of: sunday, monday
avatar_urlnullablestringURL of the profile avatar image.
planstringCurrent billing plan.
One of: free, pro
Request
curl https://skdul.ai/api/v1/profile \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "username": "janedoe",
  "full_name": "Jane Doe",
  "email": "jane@example.com",
  "timezone": "America/New_York",
  "time_format": "12h",
  "brand_color": "#E8634A",
  "bio": "Product designer based in NYC",
  "week_start": "sunday",
  "avatar_url": "https://skdul.ai/avatars/janedoe.jpg",
  "plan": "pro"
}

PATCH/api/v1/profile

Updates one or more profile fields. Only include the fields you want to change — omitted fields are left untouched. Timezone changes affect how new availability slots are calculated but do not alter existing bookings.

Returns Returns the updated Profile object.

Body Parameters
NameTypeRequiredDescription
fullNamestringNoDisplay name shown on the booking page.
timezonestringNoIANA timezone (e.g. America/New_York). Affects future slot calculations.
timeFormat"12h" | "24h"NoTime display format.
brandColorstringNoHex color code (e.g. #E8634A).
biostringNoShort bio for the public booking page (max 280 chars).
weekStart"sunday" | "monday"NoFirst day of the week on calendar views.
Request
curl -X PATCH https://skdul.ai/api/v1/profile \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{"fullName": "Jane Doe", "timezone": "America/New_York"}'
Response
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "username": "janedoe",
  "full_name": "Jane Doe",
  "timezone": "America/New_York",
  "time_format": "12h",
  "brand_color": "#E8634A",
  "bio": "Updated bio",
  "week_start": "sunday"
}

Events

4 endpoints

GET/api/v1/event-types

Returns all event types (meeting templates) for the authenticated user, sorted by creation date. Active events are included by default; pass includeInactive=true to also fetch disabled ones. Each event type defines the duration, location, buffer times, and booking constraints guests see.

Returns Returns an array of EventType objects.

Query Parameters
NameTypeRequiredDefaultDescription
includeInactivebooleanNofalseInclude disabled event types in the response.

Response Schema

FieldTypeDescription
idstring (uuid)Unique identifier for the event type.
titlestringDisplay name shown on the booking page.
slugstringURL slug. Booking page is at /{username}/{slug}.
duration_minutesnumberMeeting duration in minutes.
descriptionnullablestringDescription shown to guests before booking.
location_typestringMeeting location type.
One of: video, phone, in_person, custom
buffer_beforenumberMinutes blocked before each meeting.
buffer_afternumberMinutes blocked after each meeting.
minimum_noticenumberMinimum advance notice required in minutes.
max_per_daynullablenumberMaximum bookings of this type per day. null = unlimited.
colornullablestringDisplay color hex code.
is_activebooleanWhether this event type is open for bookings.
Request
curl "https://skdul.ai/api/v1/event-types?includeInactive=true" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
[
  {
    "id": "3b4c5d6e-7f8a-9012-bcde-f01234567890",
    "title": "30 Minute Meeting",
    "slug": "30min",
    "duration_minutes": 30,
    "description": "Quick chat to discuss your project",
    "location_type": "video",
    "buffer_before": 0,
    "buffer_after": 5,
    "minimum_notice": 60,
    "max_per_day": null,
    "color": "#E8634A",
    "is_active": true
  }
]

POST/api/v1/event-types

Creates a new event type. The slug must be unique per user and URL-safe — it forms the path of the public booking page (/{username}/{slug}). Newly created event types are active by default. Buffer times and minimum notice are in minutes.

Returns Returns the created EventType object.

Body Parameters
NameTypeRequiredDefaultDescription
titlestringYesEvent title (e.g. '30 Minute Meeting').
slugstringYesUnique URL slug (e.g. '30min'). Must be lowercase, no spaces.
durationMinutesnumberYesDuration in minutes.
descriptionstringNoDescription shown on the booking page.
locationType"video" | "phone" | "in_person" | "custom"NovideoMeeting location type.
bufferBeforenumberNo0Buffer minutes before the meeting.
bufferAfternumberNo0Buffer minutes after the meeting.
minimumNoticenumberNo60Minimum advance notice in minutes before a booking is allowed.
maxPerDaynumberNoMaximum bookings of this type per day. Omit for unlimited.
colorstringNoDisplay color hex code.
Request
curl -X POST https://skdul.ai/api/v1/event-types \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "30 Minute Meeting",
    "slug": "30min",
    "durationMinutes": 30,
    "locationType": "video",
    "bufferAfter": 5
  }'
Response
{
  "id": "3b4c5d6e-7f8a-9012-bcde-f01234567890",
  "title": "30 Minute Meeting",
  "slug": "30min",
  "duration_minutes": 30,
  "location_type": "video",
  "buffer_before": 0,
  "buffer_after": 5,
  "minimum_notice": 60,
  "is_active": true
}

PATCH/api/v1/event-types/[id]

Updates any subset of fields on an existing event type. Use isActive to enable or disable bookings without deleting the event. Slug changes take effect immediately — share updated booking links with guests.

Returns Returns the updated EventType object.

Path Parameters
NameTypeRequiredDescription
idstring (uuid)YesThe event type ID.
Body Parameters
NameTypeRequiredDescription
titlestringNoEvent title.
slugstringNoURL slug.
durationMinutesnumberNoDuration in minutes.
descriptionstringNoDescription.
locationType"video" | "phone" | "in_person" | "custom"NoLocation type.
bufferBeforenumberNoBuffer minutes before.
bufferAfternumberNoBuffer minutes after.
minimumNoticenumberNoMinimum advance notice in minutes.
maxPerDaynumberNoMaximum bookings per day.
colorstringNoDisplay color hex code.
isActivebooleanNoSet to false to stop accepting new bookings.
Request
curl -X PATCH https://skdul.ai/api/v1/event-types/<id> \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{"title": "Updated Meeting", "durationMinutes": 45}'
Response
{
  "id": "3b4c5d6e-7f8a-9012-bcde-f01234567890",
  "title": "Updated Meeting",
  "slug": "30min",
  "duration_minutes": 45,
  "is_active": true
}

DELETE/api/v1/event-types/[id]

Permanently deletes an event type. Existing confirmed bookings for this event type are not cancelled — they remain active. If you want to stop new bookings without losing history, use PATCH with isActive: false instead.

Returns Returns a success confirmation.

Path Parameters
NameTypeRequiredDescription
idstring (uuid)YesThe event type ID to delete.
Request
curl -X DELETE https://skdul.ai/api/v1/event-types/<id> \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{ "success": true }

Availability

1 endpoint

GET/api/v1/availability/slots

Returns all available time slots for a given event type within a date range. Slots respect the host's working hours, buffer times, existing bookings, and synced external calendar events. The timezone parameter controls how slot times are expressed in the response — pass the guest's timezone for best results.

Returns Returns an array of TimeSlot objects.

Query Parameters
NameTypeRequiredDefaultDescription
eventTypeIdstringYesThe event type ID to check availability for.
startDatestringYesStart of the date range (YYYY-MM-DD).
endDatestringYesEnd of the date range (YYYY-MM-DD). Max 60-day range.
timezonestringNoUTCIANA timezone for slot times (e.g. America/Chicago).

Response Schema

FieldTypeDescription
startstring (ISO 8601)Slot start time in UTC.
endstring (ISO 8601)Slot end time in UTC.
Request
curl "https://skdul.ai/api/v1/availability/slots?eventTypeId=<id>&startDate=2026-03-10&endDate=2026-03-17&timezone=America/Chicago" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
[
  { "start": "2026-03-10T14:00:00Z", "end": "2026-03-10T14:30:00Z" },
  { "start": "2026-03-10T14:30:00Z", "end": "2026-03-10T15:00:00Z" },
  { "start": "2026-03-10T15:00:00Z", "end": "2026-03-10T15:30:00Z" }
]

Bookings

5 endpoints

GET/api/v1/bookings

Returns a paginated list of bookings for the authenticated user, sorted by start time descending. By default only upcoming confirmed bookings are returned. Use status and guestEmail to narrow results. The limit parameter caps the result set; cursor-based pagination is coming.

Returns Returns an array of Booking objects.

Query Parameters
NameTypeRequiredDefaultDescription
statusstringNoFilter by status: confirmed, cancelled_by_host, cancelled_by_guest, rescheduled, completed, no_show.
upcomingbooleanNotrueWhen true, only returns bookings with a future start time.
guestEmailstringNoFilter bookings to a specific guest email address.
limitnumberNo20Maximum number of bookings to return (max 100).

Response Schema

FieldTypeDescription
idstring (uuid)Booking identifier.
event_type_idstring (uuid)The event type this booking belongs to.
start_timestring (ISO 8601)Meeting start time in UTC.
end_timestring (ISO 8601)Meeting end time in UTC.
guest_namestringGuest's full name.
guest_emailstringGuest's email address.
guest_timezonenullablestringGuest's IANA timezone at the time of booking.
statusstringCurrent booking status.
One of: confirmed, cancelled_by_host, cancelled_by_guest, rescheduled, completed, no_show
guest_notesnullablestringNotes the guest submitted when booking.
meeting_urlnullablestringVideo conference URL (if location type is video).
Request
curl "https://skdul.ai/api/v1/bookings?status=confirmed&upcoming=true&limit=10" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
[
  {
    "id": "7c8d9e0f-1a2b-3456-cdef-012345678901",
    "event_type_id": "3b4c5d6e-7f8a-9012-bcde-f01234567890",
    "start_time": "2026-03-15T14:00:00Z",
    "end_time": "2026-03-15T14:30:00Z",
    "guest_name": "John Smith",
    "guest_email": "john@example.com",
    "guest_timezone": "America/Chicago",
    "status": "confirmed",
    "guest_notes": "Looking forward to it",
    "meeting_url": "https://zoom.us/j/123456789"
  }
]

POST/api/v1/bookings

Creates a confirmed booking for a guest. The slot must be within the event type's availability window — call GET /api/v1/availability/slots first to find valid times. Returns 409 if the slot is already taken. Confirmation emails are sent automatically to both the host and guest.

Returns Returns an object with the new booking ID and event title.

Body Parameters
NameTypeRequiredDescription
eventTypeIdstringYesThe event type ID to book.
startTimestring (ISO 8601)YesMeeting start time in UTC.
endTimestring (ISO 8601)YesMeeting end time in UTC.
guestNamestringYesGuest's full name.
guestEmailstringYesGuest's email address. Used for confirmation and reminder emails.
durationMinutesnumberNoDuration in minutes. Defaults to the event type's configured duration if omitted.
guestTimezonestringNoGuest's IANA timezone. Used for email formatting.
guestNotesstringNoOptional notes from the guest.
Request
curl -X POST https://skdul.ai/api/v1/bookings \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "eventTypeId": "<event_type_id>",
    "startTime": "2026-03-15T14:00:00Z",
    "endTime": "2026-03-15T14:30:00Z",
    "guestName": "John Smith",
    "guestEmail": "john@example.com",
    "durationMinutes": 30
  }'
Response
{
  "bookingId": "7c8d9e0f-1a2b-3456-cdef-012345678901",
  "eventTitle": "30 Minute Meeting"
}

GET/api/v1/bookings/[id]

Retrieves full details for a single booking, including the nested event type metadata. Use this to display booking confirmation pages or to sync booking details to your CRM.

Returns Returns a Booking object with a nested event_types field.

Path Parameters
NameTypeRequiredDescription
idstring (uuid)YesThe booking ID.
Request
curl https://skdul.ai/api/v1/bookings/<id> \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "id": "7c8d9e0f-1a2b-3456-cdef-012345678901",
  "event_type_id": "3b4c5d6e-7f8a-9012-bcde-f01234567890",
  "start_time": "2026-03-15T14:00:00Z",
  "end_time": "2026-03-15T14:30:00Z",
  "guest_name": "John Smith",
  "guest_email": "john@example.com",
  "guest_timezone": "America/Chicago",
  "status": "confirmed",
  "guest_notes": "Looking forward to it",
  "meeting_url": "https://zoom.us/j/123456789",
  "event_types": {
    "title": "30 Minute Meeting",
    "duration_minutes": 30,
    "location_type": "video"
  }
}

POST/api/v1/bookings/[id]/cancel

Cancels a confirmed booking. Sets the status to cancelled_by_host, removes the event from the host's connected calendar, deletes the Zoom meeting if applicable, and sends a cancellation email to the guest. This action is irreversible — reschedule instead if a time change is needed.

Returns Returns a success confirmation. The booking status is updated to cancelled_by_host.

Path Parameters
NameTypeRequiredDescription
idstring (uuid)YesThe booking ID to cancel.
Body Parameters
NameTypeRequiredDescription
reasonstringNoCancellation reason included in the guest notification email.
Request
curl -X POST https://skdul.ai/api/v1/bookings/<id>/cancel \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Schedule conflict"}'
Response
{ "success": true }

POST/api/v1/bookings/[id]/reschedule

Reschedules an existing booking to a new time slot. Creates a new confirmed booking at the new time, marks the original as rescheduled, updates the calendar event and Zoom meeting, and sends rescheduling emails to both parties. The new slot must be available.

Returns Returns a success flag and the new booking's ID.

Path Parameters
NameTypeRequiredDescription
idstring (uuid)YesThe booking ID to reschedule.
Body Parameters
NameTypeRequiredDescription
newStartTimestring (ISO 8601)YesNew start time in UTC.
newEndTimestring (ISO 8601)YesNew end time in UTC.
Request
curl -X POST https://skdul.ai/api/v1/bookings/<id>/reschedule \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "newStartTime": "2026-03-16T10:00:00Z",
    "newEndTime": "2026-03-16T10:30:00Z"
  }'
Response
{
  "success": true,
  "newBookingId": "e5f6a7b8-c9d0-1234-efab-345678901234"
}

Schedule

2 endpoints

GET/api/v1/schedule

Returns the host's weekly availability schedule — the days and hours during which bookings can be made. The schedule is used to compute available slots in conjunction with buffer times, minimum notice, and external calendar busy times.

Returns Returns a Schedule object with a rules array.

Request
curl https://skdul.ai/api/v1/schedule \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "schedule": {
    "id": "5d6e7f8a-9b0c-1234-6789-abcdef012345",
    "name": "Default",
    "timezone": "America/New_York"
  },
  "rules": [
    { "day_of_week": 1, "start_time": "09:00", "end_time": "17:00", "is_enabled": true },
    { "day_of_week": 2, "start_time": "09:00", "end_time": "17:00", "is_enabled": true },
    { "day_of_week": 3, "start_time": "09:00", "end_time": "17:00", "is_enabled": true },
    { "day_of_week": 4, "start_time": "09:00", "end_time": "17:00", "is_enabled": true },
    { "day_of_week": 5, "start_time": "09:00", "end_time": "17:00", "is_enabled": true }
  ]
}

PUT/api/v1/schedule

Replaces the weekly availability schedule. Send the full rules array — this is a full replacement, not a merge. Days not included, or with isEnabled: false, will block all bookings on that day. Day numbering follows ISO 8601: 0 = Sunday, 6 = Saturday.

Returns Returns the updated Schedule object.

Body Parameters
NameTypeRequiredDefaultDescription
rulesAvailabilityRule[]YesFull set of availability rules. Replaces all existing rules.
rules[].dayOfWeeknumber (0–6)YesDay of week. 0 = Sunday, 1 = Monday … 6 = Saturday.
rules[].startTimestringYesStart of availability window (HH:MM, 24h format).
rules[].endTimestringYesEnd of availability window (HH:MM, 24h format).
rules[].isEnabledbooleanNotrueSet to false to block this day without removing the rule.
Request
curl -X PUT https://skdul.ai/api/v1/schedule \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [
      { "dayOfWeek": 1, "startTime": "09:00", "endTime": "17:00" },
      { "dayOfWeek": 3, "startTime": "09:00", "endTime": "17:00" },
      { "dayOfWeek": 5, "startTime": "09:00", "endTime": "17:00" }
    ]
  }'
Response
{
  "schedule": { "id": "5d6e7f8a-9b0c-1234-6789-abcdef012345", "name": "Default" },
  "rules": [
    { "day_of_week": 1, "start_time": "09:00", "end_time": "17:00", "is_enabled": true },
    { "day_of_week": 3, "start_time": "09:00", "end_time": "17:00", "is_enabled": true },
    { "day_of_week": 5, "start_time": "09:00", "end_time": "17:00", "is_enabled": true }
  ]
}

GET/api/v1/calendar/health

Analyzes your calendar over a date range and returns a weighted health score (0–100) with detailed breakdown. The score factors in meeting density, back-to-back sequences, missing buffers, and available focus time. Use this to surface calendar burn-out signals in dashboards or AI assistants.

Returns Returns a CalendarHealthResult object.

Query Parameters
NameTypeRequiredDefaultDescription
startDatestringNo14 days agoStart of analysis range (YYYY-MM-DD).
endDatestringNo14 days aheadEnd of analysis range (YYYY-MM-DD).
timezonestringNoUTCIANA timezone for date boundary calculations.
Request
curl "https://skdul.ai/api/v1/calendar/health?startDate=2026-03-01&endDate=2026-03-14&timezone=America/New_York" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "score": 72,
  "period": { "startDate": "2026-03-01", "endDate": "2026-03-14" },
  "totalMeetings": 24,
  "totalHoursBooked": 12.5,
  "avgMeetingsPerDay": 1.7,
  "backToBackCount": 6,
  "noBufferCount": 3,
  "focusTimeHours": 18.2,
  "longestFocusBlock": 180,
  "overbookedDays": [
    { "date": "2026-03-05", "count": 7 }
  ],
  "recommendations": [
    "Add buffer time after meetings on Tuesdays",
    "Consider blocking Thursday mornings for deep work"
  ]
}

GET/api/v1/calendar/conflicts

Detects double-booked or overlapping events in your calendar within a date range. Returns each conflicting pair with the overlapping time range. Useful for surfacing scheduling mistakes or auditing calendar state before important periods.

Returns Returns an object with a conflicts array.

Query Parameters
NameTypeRequiredDefaultDescription
startDatestringNotodayStart of detection range (YYYY-MM-DD).
endDatestringNo7 days aheadEnd of detection range (YYYY-MM-DD).
Request
curl "https://skdul.ai/api/v1/calendar/conflicts?startDate=2026-03-10&endDate=2026-03-17" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "conflicts": [
    {
      "eventA": { "id": "7c8d9e0f-1a2b-3456-cdef-012345678901", "title": "Team Standup", "start": "2026-03-10T14:00:00Z" },
      "eventB": { "id": "8d9e0f1a-2b3c-4567-def0-123456789012", "title": "Client Call", "start": "2026-03-10T14:15:00Z" },
      "overlapMinutes": 15
    }
  ],
  "count": 1
}

GET/api/v1/calendar/focus-time

Finds uninterrupted blocks of free time in your calendar — ideal for deep work or batch scheduling. Blocks shorter than minBlockMinutes are excluded. Pass preferredStartHour and preferredEndHour to restrict results to a specific part of the day.

Returns Returns an array of FocusBlock objects.

Query Parameters
NameTypeRequiredDefaultDescription
startDatestringNotodayStart of search range (YYYY-MM-DD).
endDatestringNo7 days aheadEnd of search range (YYYY-MM-DD).
minBlockMinutesnumberNo90Minimum block duration in minutes.
preferredStartHournumber (0–23)NoOnly return blocks starting at or after this hour.
preferredEndHournumber (0–23)NoOnly return blocks ending at or before this hour.
timezonestringNoUTCIANA timezone for hour filtering.
Request
curl "https://skdul.ai/api/v1/calendar/focus-time?minBlockMinutes=120&startDate=2026-03-10&endDate=2026-03-17" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "focusBlocks": [
    {
      "start": "2026-03-11T09:00:00Z",
      "end": "2026-03-11T12:00:00Z",
      "durationMinutes": 180,
      "date": "2026-03-11"
    },
    {
      "start": "2026-03-12T13:30:00Z",
      "end": "2026-03-12T16:00:00Z",
      "durationMinutes": 150,
      "date": "2026-03-12"
    }
  ],
  "totalFocusHours": 5.5
}

GET/api/v1/calendar/week-summary

Returns a structured summary of a single week's meetings, hours, and load distribution. Defaults to the current week. Use weekOf to inspect any past or future week. Results are segmented by day for charting.

Returns Returns a WeekSummary object.

Query Parameters
NameTypeRequiredDefaultDescription
weekOfstringNocurrent weekAny date within the target week (YYYY-MM-DD).
timezonestringNoUTCIANA timezone for day boundary calculations.
Request
curl "https://skdul.ai/api/v1/calendar/week-summary?weekOf=2026-03-09&timezone=America/New_York" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "weekOf": "2026-03-09",
  "totalMeetings": 11,
  "totalHours": 5.5,
  "byDay": [
    { "date": "2026-03-09", "dayName": "Monday", "meetings": 3, "hours": 1.5, "backToBack": 1 },
    { "date": "2026-03-10", "dayName": "Tuesday", "meetings": 2, "hours": 1.0, "backToBack": 0 },
    { "date": "2026-03-11", "dayName": "Wednesday", "meetings": 4, "hours": 2.0, "backToBack": 2 },
    { "date": "2026-03-12", "dayName": "Thursday", "meetings": 1, "hours": 0.5, "backToBack": 0 },
    { "date": "2026-03-13", "dayName": "Friday", "meetings": 1, "hours": 0.5, "backToBack": 0 }
  ]
}

GET/api/v1/calendar/suggest-reschedules

Analyses upcoming back-to-back meetings and overloaded days, then suggests specific bookings that could be moved and better times for them. Powered by the same scoring engine as smart booking. Useful for proactive calendar management in AI assistants.

Returns Returns an array of RescheduleRecommendation objects.

Query Parameters
NameTypeRequiredDefaultDescription
timezonestringNoUTCIANA timezone for suggestion context.
Request
curl "https://skdul.ai/api/v1/calendar/suggest-reschedules?timezone=America/New_York" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "suggestions": [
    {
      "booking": {
        "id": "7c8d9e0f-1a2b-3456-cdef-012345678901",
        "title": "Weekly Sync",
        "start": "2026-03-11T09:00:00Z"
      },
      "reason": "Back-to-back with prior meeting, no buffer",
      "suggestedSlots": [
        { "start": "2026-03-11T11:00:00Z", "end": "2026-03-11T12:00:00Z" },
        { "start": "2026-03-12T14:00:00Z", "end": "2026-03-12T15:00:00Z" }
      ]
    }
  ]
}

POST/api/v1/calendar/sync

Triggers a manual sync of the host's connected Google Calendar to refresh busy time data. By default syncs the last 7 days and next 30 days. Use forceFullSync for a complete historical resync (slower). The availability/slots endpoint uses this data automatically.

Returns Returns a sync result summary.

Body Parameters
NameTypeRequiredDefaultDescription
daysBacknumberNo7Days back from today to sync.
daysAheadnumberNo30Days ahead to sync.
forceFullSyncbooleanNofalseIf true, clears and resyncs all busy times (slower).
Request
curl -X POST https://skdul.ai/api/v1/calendar/sync \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{"daysAhead": 60}'
Response
{
  "synced": true,
  "eventsProcessed": 42,
  "daysBack": 7,
  "daysAhead": 30
}

Preferences

3 endpoints

GET/api/v1/preferences

Lists the authenticated user's scheduling preference rules. Rules can be explicit (set via POST) or learned (inferred by the AI from booking patterns). Use includeLearnedRules=false to retrieve only manually set rules. Preferences power the smart booking slot scoring engine.

Returns Returns an array of Preference objects.

Query Parameters
NameTypeRequiredDefaultDescription
ruleTypestringNoFilter by rule type: time_preference, day_preference, contact_rule, focus_block, max_meetings, buffer_rule, duration_rule, custom.
contactEmailstringNoFilter to preferences for a specific contact.
includeLearnedRulesbooleanNotrueWhether to include AI-inferred rules alongside explicit ones.

Response Schema

FieldTypeDescription
idstring (uuid)Preference rule identifier.
rule_typestringCategory of the rule.
One of: time_preference, day_preference, contact_rule, focus_block, max_meetings, buffer_rule, duration_rule, custom
ruleobjectRule payload — structure varies by rule_type.
contact_emailnullablestringIf set, rule applies only to this contact.
contact_categorynullablestringIf set, rule applies to contacts in this category (e.g. VIP, teammate).
prioritynumberHigher values take precedence when rules conflict.
descriptionnullablestringHuman-readable description of the rule.
is_learnedbooleanTrue if inferred by AI, false if manually created.
Request
curl "https://skdul.ai/api/v1/preferences?includeLearnedRules=true" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
[
  {
    "id": "2a3b4c5d-6e7f-8901-abcd-ef1234567890",
    "rule_type": "time_preference",
    "rule": { "preferMorning": true, "earliestHour": 9, "latestHour": 12 },
    "contact_email": null,
    "priority": 5,
    "description": "Prefer morning slots",
    "is_learned": false
  },
  {
    "id": "3b4c5d6e-7f8a-9012-bcde-f12345678901",
    "rule_type": "contact_rule",
    "rule": { "preferDays": [1, 3] },
    "contact_email": "vip@client.com",
    "priority": 10,
    "description": "Meet this client on Mon or Wed",
    "is_learned": false
  }
]

POST/api/v1/preferences

Creates a new scheduling preference rule. Rules are applied by the slot scoring engine when computing best available times. Higher priority rules override lower ones when they conflict. Contact-scoped rules only apply when scheduling with that specific contact.

Returns Returns the created Preference object.

Body Parameters
NameTypeRequiredDefaultDescription
ruleTypestringYesRule category: time_preference, day_preference, contact_rule, focus_block, max_meetings, buffer_rule, duration_rule, custom.
ruleobjectYesRule payload object. Structure depends on ruleType.
contactEmailstringNoApply this rule only to a specific contact.
contactCategorystringNoApply this rule to a category of contacts.
eventTypeIdstringNoScope this rule to a specific event type.
prioritynumberNo5Rule priority (1–10). Higher values take precedence.
descriptionstringNoHuman-readable label for this rule.
Request
curl -X POST https://skdul.ai/api/v1/preferences \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "ruleType": "time_preference",
    "rule": { "preferMorning": true, "earliestHour": 9, "latestHour": 12 },
    "priority": 5,
    "description": "Prefer morning slots"
  }'
Response
{
  "id": "2a3b4c5d-6e7f-8901-abcd-ef1234567890",
  "rule_type": "time_preference",
  "rule": { "preferMorning": true, "earliestHour": 9, "latestHour": 12 },
  "priority": 5,
  "description": "Prefer morning slots",
  "is_learned": false,
  "created_at": "2026-03-01T10:00:00Z"
}

DELETE/api/v1/preferences/[id]

Deletes a preference rule. The deletion takes effect immediately — subsequent slot scoring calls will no longer apply this rule. Learned rules can be deleted to override AI-inferred preferences.

Returns Returns a success confirmation.

Path Parameters
NameTypeRequiredDescription
idstring (uuid)YesThe preference ID to delete.
Request
curl -X DELETE https://skdul.ai/api/v1/preferences/<id> \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{ "success": true }

Insights

2 endpoints

GET/api/v1/insights

Returns AI-computed scheduling insights derived from booking history. Insights cover patterns like busiest days, peak hours, back-to-back frequency, and weekly meeting load. Results are cached — call POST /api/v1/insights/recompute to refresh. Each insight includes a confidence score (0–1) based on data volume.

Returns Returns an array of Insight objects.

Query Parameters
NameTypeRequiredDescription
insightTypestringNoFilter to a specific insight type: busiest_day, peak_hours, back_to_back, weekly_load, duration_pattern.
Request
curl "https://skdul.ai/api/v1/insights?insightType=busiest_day" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
[
  {
    "insightType": "busiest_day",
    "summary": "Tuesday is your busiest day with 8 meetings on average",
    "data": { "busiestDayName": "Tuesday", "busiestDayOfWeek": 2, "dayCounts": { "0": 1, "1": 5, "2": 8 } },
    "confidence": 0.87
  },
  {
    "insightType": "peak_hours",
    "summary": "Most meetings are scheduled between 10am–12pm",
    "data": { "peakStartHour": 10, "peakEndHour": 12, "hourCounts": { "9": 3, "10": 12, "11": 14, "12": 7 } },
    "confidence": 0.91
  }
]

POST/api/v1/insights/recompute

Triggers a fresh recompute of all scheduling insights from the last 90 days of booking history. Recomputation is synchronous and typically takes under 500ms. Use this after importing a large batch of historical bookings or when insights feel stale.

Returns Returns an array of freshly computed Insight objects.

Request
curl -X POST https://skdul.ai/api/v1/insights/recompute \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
[
  {
    "insightType": "busiest_day",
    "summary": "Wednesday is your busiest day with 6 meetings on average",
    "data": { "busiestDayName": "Wednesday", "busiestDayOfWeek": 3 },
    "confidence": 0.82
  }
]

Contacts

1 endpoint

GET/api/v1/contacts/[email]/context

Returns the full scheduling history and learned context for a specific contact email. Includes past bookings, observed time preferences, and any contact-scoped preference rules. Use this before creating a booking request to surface the most convenient times for both parties.

Returns Returns a ContactContext object.

Path Parameters
NameTypeRequiredDescription
emailstring (URL-encoded)YesThe contact's email address, URL-encoded.
Request
curl "https://skdul.ai/api/v1/contacts/john%40example.com/context" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "email": "john@example.com",
  "totalMeetings": 8,
  "lastMeetingAt": "2026-02-28T14:00:00Z",
  "preferredDays": [1, 3],
  "preferredHourRange": { "start": 10, "end": 14 },
  "recentBookings": [
    {
      "id": "7c8d9e0f-1a2b-3456-cdef-012345678901",
      "start_time": "2026-02-28T14:00:00Z",
      "event_title": "30 Minute Meeting"
    }
  ],
  "rules": [
    {
      "id": "2a3b4c5d-6e7f-8901-abcd-ef1234567890",
      "rule_type": "contact_rule",
      "description": "Meet on Mon or Wed"
    }
  ]
}

GET/api/v1/scheduling-requests

Lists scheduling requests where you are either the initiator or the recipient. Use direction to filter to sent or received requests. Pending requests have status=pending; accepted ones transition to accepted and generate a booking automatically.

Returns Returns an array of SchedulingRequest objects.

Query Parameters
NameTypeRequiredDefaultDescription
directionstringNoallWhich requests to return: sent, received, all.
statusstringNoFilter by request status: pending, accepted, rejected, cancelled, expired.
limitnumberNo20Maximum number of results.

Response Schema

FieldTypeDescription
idstring (uuid)Scheduling request identifier.
initiator_user_idstringUser ID of the person who sent the request.
target_usernamestringUsername of the request recipient.
titlestringMeeting title proposed by the initiator.
duration_minutesnumberProposed meeting duration.
statusstringCurrent status.
One of: pending, accepted, rejected, cancelled, expired
matched_slot_startnullablestring (ISO 8601)The mutually available slot found by the engine.
matched_slot_endnullablestring (ISO 8601)End of the matched slot.
notesnullablestringOptional context message from the initiator.
created_atstring (ISO 8601)When the request was created.
Request
curl "https://skdul.ai/api/v1/scheduling-requests?direction=sent&status=pending" \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
[
  {
    "id": "9e0f1a2b-3c4d-5678-defa-123456789012",
    "initiator_user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "target_username": "janedoe",
    "title": "Product feedback session",
    "duration_minutes": 30,
    "status": "pending",
    "matched_slot_start": "2026-03-15T15:00:00Z",
    "matched_slot_end": "2026-03-15T15:30:00Z",
    "notes": "Would love to get your thoughts on the new feature",
    "created_at": "2026-03-10T10:00:00Z"
  }
]

POST/api/v1/scheduling-requests

Creates a new scheduling request targeting another skdul user. The engine automatically finds mutual availability and proposes a matched slot — the recipient can accept or reject it. A notification email is sent to the target. Returns 400 if no mutual availability can be found.

Returns Returns the created SchedulingRequest object with a matched slot.

Body Parameters
NameTypeRequiredDefaultDescription
targetUsernamestringYesThe username of the person you want to schedule with.
titlestringYesMeeting title shown to the recipient.
durationMinutesnumberNo30Desired meeting duration in minutes.
locationTypestringNovideoPreferred meeting format: video, phone, in_person.
notesstringNoOptional context message to the recipient.
preferencesobjectNoOptional scheduling preferences (preferMorning, avoidDays, etc.) for slot matching.
Request
curl -X POST https://skdul.ai/api/v1/scheduling-requests \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "targetUsername": "janedoe",
    "title": "Product feedback session",
    "durationMinutes": 30,
    "notes": "Would love to get your thoughts on the new feature"
  }'
Response
{
  "requestId": "9e0f1a2b-3c4d-5678-defa-123456789012",
  "status": "pending",
  "matchedSlotStart": "2026-03-15T15:00:00Z",
  "matchedSlotEnd": "2026-03-15T15:30:00Z"
}

GET/api/v1/scheduling-requests/[id]

Retrieves full details for a specific scheduling request including the matched slot and current status. Use this to poll request state or display booking confirmation UI.

Returns Returns a SchedulingRequest object.

Path Parameters
NameTypeRequiredDescription
idstring (uuid)YesThe scheduling request ID.
Request
curl https://skdul.ai/api/v1/scheduling-requests/<id> \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "id": "9e0f1a2b-3c4d-5678-defa-123456789012",
  "target_username": "janedoe",
  "title": "Product feedback session",
  "duration_minutes": 30,
  "status": "pending",
  "matched_slot_start": "2026-03-15T15:00:00Z",
  "matched_slot_end": "2026-03-15T15:30:00Z",
  "notes": "Would love to get your thoughts",
  "created_at": "2026-03-10T10:00:00Z"
}

POST/api/v1/scheduling-requests/[id]/respond

Accepts or rejects an incoming scheduling request. Accepting automatically creates a confirmed booking at the matched slot and notifies the initiator. Rejecting sends a rejection notification with an optional reason. Only the request recipient can respond.

Returns Accept: returns a Booking object. Reject: returns updated SchedulingRequest with status: rejected.

Path Parameters
NameTypeRequiredDescription
idstring (uuid)YesThe scheduling request ID to respond to.
Body Parameters
NameTypeRequiredDescription
action"accept" | "reject"YesWhether to accept or reject the request.
rejectionReasonstringNoOptional explanation sent to the initiator when rejecting.
Request
curl -X POST https://skdul.ai/api/v1/scheduling-requests/<id>/respond \
  -H "Authorization: Bearer sk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{"action": "accept"}'
Response
{
  "bookingId": "7c8d9e0f-1a2b-3456-cdef-012345678901",
  "status": "accepted",
  "start_time": "2026-03-15T15:00:00Z",
  "end_time": "2026-03-15T15:30:00Z"
}

POST/api/v1/scheduling-requests/[id]/cancel

Cancels an outgoing scheduling request that has not yet been accepted. Can be called by the initiator. If the request was already accepted and a booking created, cancel the booking directly via POST /api/v1/bookings/{id}/cancel.

Returns Returns the cancelled SchedulingRequest with status: cancelled.

Path Parameters
NameTypeRequiredDescription
idstring (uuid)YesThe scheduling request ID to cancel.
Request
curl -X POST https://skdul.ai/api/v1/scheduling-requests/<id>/cancel \
  -H "Authorization: Bearer sk_live_<your_key>"
Response
{
  "id": "9e0f1a2b-3c4d-5678-defa-123456789012",
  "status": "cancelled"
}

Public Endpoints

No auth required3 endpoints

GET/api/v1/public/[username]/[slug]

Returns a public event type for a given host username and event slug — no API key required. Use this to build embedded booking widgets or fetch event metadata for display. Also returns the host's public profile.

Returns Returns a public EventType with host profile embedded.

Path Parameters
NameTypeRequiredDescription
usernamestringYesThe host's skdul username.
slugstringYesThe event type slug.
Request
curl https://skdul.ai/api/v1/public/janedoe/30min
Response
{
  "event": {
    "id": "3b4c5d6e-7f8a-9012-bcde-f01234567890",
    "title": "30 Minute Meeting",
    "slug": "30min",
    "duration_minutes": 30,
    "description": "Quick chat",
    "location_type": "video"
  },
  "host": {
    "username": "janedoe",
    "full_name": "Jane Doe",
    "bio": "Product designer",
    "avatar_url": "https://skdul.ai/avatars/janedoe.jpg",
    "brand_color": "#E8634A"
  }
}

GET/api/v1/public/[username]/event-types

Lists all active public event types for a given host. No authentication required. Use this to populate a booking page with all available meeting types.

Returns Returns an array of public EventType objects.

Path Parameters
NameTypeRequiredDescription
usernamestringYesThe host's skdul username.
Request
curl https://skdul.ai/api/v1/public/janedoe/event-types
Response
[
  { "id": "3b4c5d6e-7f8a-9012-bcde-f01234567890", "title": "30 Minute Meeting", "slug": "30min", "duration_minutes": 30 },
  { "id": "4c5d6e7f-8a9b-0123-cdef-012345678901", "title": "60 Minute Strategy Call", "slug": "strategy", "duration_minutes": 60 }
]

POST/api/v1/public/bookings

Creates a booking on behalf of a guest without an API key. Intended for embedding booking flows in third-party sites. Requires a hostId (the host's user ID, not API key) alongside the usual booking fields. Returns 409 if the slot is no longer available.

Returns Returns the created Booking object.

Body Parameters
NameTypeRequiredDescription
hostIdstring (uuid)YesThe host's user ID. Obtain via GET /api/v1/public/[username]/[slug].
eventTypeIdstringYesEvent type ID.
startTimestring (ISO 8601)YesStart time in UTC.
endTimestring (ISO 8601)YesEnd time in UTC.
guestNamestringYesGuest's full name.
guestEmailstringYesGuest's email address.
durationMinutesnumberYesDuration in minutes.
guestTimezonestringNoGuest's IANA timezone.
guestNotesstringNoOptional notes from the guest.
Request
curl -X POST https://skdul.ai/api/v1/public/bookings \
  -H "Content-Type: application/json" \
  -d '{
    "hostId": "<host_user_id>",
    "eventTypeId": "<event_type_id>",
    "startTime": "2026-03-15T14:00:00Z",
    "endTime": "2026-03-15T14:30:00Z",
    "guestName": "John Smith",
    "guestEmail": "john@example.com",
    "durationMinutes": 30
  }'
Response
{
  "id": "7c8d9e0f-1a2b-3456-cdef-012345678901",
  "event_type_id": "3b4c5d6e-7f8a-9012-bcde-f01234567890",
  "start_time": "2026-03-15T14:00:00Z",
  "end_time": "2026-03-15T14:30:00Z",
  "guest_name": "John Smith",
  "guest_email": "john@example.com",
  "status": "confirmed"
}

Frequently Asked Questions

How do I authenticate with the skdul API?
All /api/v1/* endpoints require an API key passed via the Authorization header as a Bearer token. Generate a key from your dashboard's MCP Server page. Format: Authorization: Bearer sk_live_<your_key>.
What is the base URL for the skdul API?
The production base URL is https://skdul.ai/api/v1. For local development, use http://localhost:3000/api/v1.
Does the skdul API have rate limits?
The API currently does not enforce hard rate limits, but we recommend keeping requests under 60 per minute per API key. Excessive usage may be throttled with a 429 response that includes a Retry-After header.
How do I get available time slots for booking?
Use GET /api/v1/availability/slots with eventTypeId, startDate, and endDate parameters. The response returns an array of available time slots with start and end times in ISO 8601 format. Pass the guest's timezone for localised results.
Can I create a booking via the API?
Yes. Use POST /api/v1/bookings with eventTypeId, startTime, endTime, guestName, guestEmail, and durationMinutes. The API validates slot availability and returns 409 if the slot is taken. Confirmation emails are sent automatically.
Are there public endpoints that don't require authentication?
Yes. GET /api/v1/public/[username]/[slug], GET /api/v1/public/[username]/event-types, and POST /api/v1/public/bookings are unauthenticated. The public booking endpoint requires a hostId parameter instead of inferring the host from an API key.
What error format does the API use?
All errors return a structured JSON object with an "error" key containing four fields: type (the error category, e.g. invalid_request_error), code (the machine-readable cause to switch on, e.g. missing_required_param), message (a human-readable explanation), and doc_url (a link to this reference). When a specific parameter caused the error, a param field is also included. HTTP status codes follow standard conventions: 400, 401, 403, 404, 409, 429, and 500.
Is there a webhook for new bookings?
Webhooks are on the roadmap but not yet available. Currently, you can poll GET /api/v1/bookings with the upcoming=true filter to check for new bookings.
What are Scheduling Requests?
Scheduling Requests let two skdul users negotiate a meeting time asynchronously. The API automatically finds mutual availability and proposes a slot — the recipient can accept or reject it. This is useful for AI-driven scheduling flows where you want to avoid back-and-forth emails.
How does Calendar Intelligence work?
The calendar intelligence endpoints (health, conflicts, focus-time, week-summary) analyse your busy_times data from synced Google Calendar events alongside skdul bookings. They surface actionable insights without requiring access to raw calendar data.

Ready to try it yourself?

Set up your booking page in two minutes. No credit card required.

Start building with skdul
Free forever plan MCP server included
Ask AI about skdul