Astrocal
Guides

Event Types

Create and manage bookable event types with duration, pricing, and booking caps.

Event types are templates for bookable meetings. Each event type defines a duration, availability rules, and optional settings like pricing, buffer times, and booking caps.

Prerequisites

Creating an event type

Create an event type by sending a POST request to /v1/event-types:

curl -X POST https://api.astrocal.dev/v1/event-types \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "30 Minute Meeting",
    "slug": "30-min-meeting",
    "duration_minutes": 30
  }'
const response = await fetch("https://api.astrocal.dev/v1/event-types", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "30 Minute Meeting",
    slug: "30-min-meeting",
    duration_minutes: 30,
  }),
});
const data = await response.json();

Try it in the API playground →

Required fields

FieldTypeDescription
titlestringDisplay name (1-255 characters)
slugstringURL-safe identifier (lowercase, hyphens only)
duration_minutesnumberMeeting length in minutes (5-480)

Optional fields

FieldTypeDefaultDescription
descriptionstringnullEvent type description (max 1000 chars)
buffer_before_minutesnumber0Buffer time before meetings (0-120 minutes)
buffer_after_minutesnumber0Buffer time after meetings (0-120 minutes)
colorstring"#3b82f6"Hex color for calendar display
max_attendeesnumber1Maximum attendees per slot (1-100). Values > 1 enable group bookings.
duration_optionsnumber[]nullAvailable durations for variable-length bookings (e.g., [15, 30, 60])
price_amountnumber|nullnullPrice in cents (min 100). See Payments
price_currencystring"usd"3-letter ISO currency code
reminder_intervalsnumber[][1440, 60]Reminder email intervals in minutes before the meeting
booking_capnumber|nullnullMaximum bookings allowed per period (1-10000)
booking_cap_periodstring|nullnullCap period: "daily", "weekly", "monthly", or "total"
booking_page_enabledbooleanfalseShow this event type on your organization's public booking page
conferencing_providerstring|nullnull"zoom", "google_meet", "microsoft_teams", "custom", or "in_person". See Conferencing
custom_meeting_urlstring|nullnullStatic meeting URL — required when conferencing_provider is "custom"
locationstring|nullnullPhysical address — used when conferencing_provider is "in_person"

Updating an event type

Update an event type with a PATCH request. Only include the fields you want to change:

curl -X PATCH https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Quick Chat",
    "duration_minutes": 15,
    "active": false
  }'
const response = await fetch(
  "https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID",
  {
    method: "PATCH",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: "Quick Chat",
      duration_minutes: 15,
      active: false,
    }),
  }
);
const data = await response.json();

Try it in the API playground →

The active field is only available on update (not creation). Setting active: false hides the event type from public availability queries.

Booking caps

Booking caps let you limit the number of bookings an event type can receive within a time period. This is useful for limiting consultation availability, capping class sizes per week, or restricting total bookings.

Setting a booking cap

Both booking_cap and booking_cap_period must be set together (or both null):

# Limit to 20 bookings per week
curl -X PATCH https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "booking_cap": 20,
    "booking_cap_period": "weekly"
  }'
const response = await fetch(
  "https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID",
  {
    method: "PATCH",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      booking_cap: 20,
      booking_cap_period: "weekly",
    }),
  }
);
const data = await response.json();

Try it in the API playground →

Cap periods

PeriodDescription
dailyResets at midnight in the event type's timezone
weeklyResets at the start of each week (Monday) in the event type's timezone
monthlyResets at the start of each month in the event type's timezone
totalNever resets -- counts all bookings since the cap was set

How caps are enforced

  1. Availability layer: When a cap is reached, the availability endpoint returns an empty slots array and a capped: true flag. This lets your UI show that the event type is fully booked for the period.

  2. Booking layer: If a booking is attempted after the cap is reached (e.g., due to a race condition), the API returns a 409 Conflict with error code booking_cap_reached.

Checking remaining capacity

The event type response includes a bookings_remaining field when a cap is configured:

curl https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID \
  -H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
  "https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID",
  {
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
    },
  }
);
const data = await response.json();

Try it in the API playground →

# Response (with booking cap)
{
  "id": "...",
  "title": "Weekly Consultation",
  "booking_cap": 20,
  "booking_cap_period": "weekly",
  "bookings_remaining": 14,
  ...
}

bookings_remaining is null when no cap is configured.

Removing a booking cap

Set both fields to null:

curl -X PATCH https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "booking_cap": null,
    "booking_cap_period": null
  }'
const response = await fetch(
  "https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID",
  {
    method: "PATCH",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      booking_cap: null,
      booking_cap_period: null,
    }),
  }
);
const data = await response.json();

Try it in the API playground →

Variable-duration bookings

To let invitees choose between multiple meeting lengths, set duration_options:

curl -X POST https://api.astrocal.dev/v1/event-types \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Consultation",
    "slug": "consultation",
    "duration_minutes": 30,
    "duration_options": [15, 30, 60]
  }'
const response = await fetch("https://api.astrocal.dev/v1/event-types", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Consultation",
    slug: "consultation",
    duration_minutes: 30,
    duration_options: [15, 30, 60],
  }),
});
const data = await response.json();

Try it in the API playground →

duration_minutes is the default duration and must be included in duration_options. When checking availability, pass duration to get slots for a specific length.

Conferencing

Set conferencing_provider to automatically attach a meeting link or location to every booking. The resulting link is returned as meeting_url on the booking, included in confirmation emails and the .ics calendar attachment.

ProviderBehaviourPrerequisite
zoomA unique Zoom meeting is created for each bookingZoom connected in the dashboard
google_meetA unique Google Meet link is generated on the calendar event for each bookingAn active Google Calendar connection
microsoft_teamsA unique Microsoft Teams link is generated on the calendar event for each bookingAn active Microsoft calendar connection
customThe static custom_meeting_url is reused for every bookingcustom_meeting_url set
in_personNo meeting link; the location address is attached to bookings insteadlocation set

Google Meet

Google Meet piggybacks on your organization's Google Calendar connection — no separate account or OAuth grant is needed. When a booking is created, Astrocal creates the calendar event with a Meet conference attached and returns the join URL synchronously:

curl -X POST https://api.astrocal.dev/v1/event-types \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Intro Call",
    "slug": "intro-call",
    "duration_minutes": 30,
    "conferencing_provider": "google_meet"
  }'
const response = await fetch("https://api.astrocal.dev/v1/event-types", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Intro Call",
    slug: "intro-call",
    duration_minutes: 30,
    conferencing_provider: "google_meet",
  }),
});
const data = await response.json();

Try it in the API playground →

Bookings for this event type then include the generated link:

# POST /v1/bookings response (201)
{
  "id": "...",
  "meeting_url": "https://meet.google.com/abc-defg-hij",
  ...
}

Notes:

  • Requires an active Google Calendar connection — without one, creating or updating the event type returns 422 with error code no_google_calendar_connection.
  • For paid bookings, the Meet link is generated after payment succeeds (while the booking is pending_payment, meeting_url is null).
  • Rescheduling preserves the original Meet link; cancelling removes the calendar event and its link.
  • Meeting link generation is fail-open: if Google is temporarily unavailable, the booking still succeeds with meeting_url: null.

Microsoft Teams

Microsoft Teams piggybacks on your organization's Microsoft calendar connection — no separate account or OAuth grant is needed. When a booking is created, Astrocal creates the Outlook calendar event with a Teams meeting attached and returns the join URL synchronously:

curl -X POST https://api.astrocal.dev/v1/event-types \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Intro Call",
    "slug": "intro-call",
    "duration_minutes": 30,
    "conferencing_provider": "microsoft_teams"
  }'
const response = await fetch("https://api.astrocal.dev/v1/event-types", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Intro Call",
    slug: "intro-call",
    duration_minutes: 30,
    conferencing_provider: "microsoft_teams",
  }),
});
const data = await response.json();

Try it in the API playground →

Bookings for this event type then include the generated link:

# POST /v1/bookings response (201)
{
  "id": "...",
  "meeting_url": "https://teams.microsoft.com/l/meetup-join/...",
  ...
}

Notes:

  • Requires an active Microsoft calendar connection — without one, creating or updating the event type returns 422 with error code no_microsoft_calendar_connection.
  • Teams links require a Microsoft 365 work or school account. Personal Microsoft accounts cannot create Teams meetings — the booking still succeeds with meeting_url: null.
  • For paid bookings, the Teams link is generated after payment succeeds (while the booking is pending_payment, meeting_url is null).
  • Rescheduling preserves the original Teams link; cancelling removes the calendar event and its meeting.
  • Meeting link generation is fail-open: if Microsoft is temporarily unavailable or the join URL hasn't finished provisioning, the booking still succeeds with meeting_url: null (the Outlook event itself usually shows the Teams link once provisioning completes).

Listing event types

curl https://api.astrocal.dev/v1/event-types \
  -H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch("https://api.astrocal.dev/v1/event-types", {
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
  },
});
const data = await response.json();

Try it in the API playground →

Filter by active status with ?active=true or ?active=false. Uses cursor-based pagination with starting_after and limit.

Deleting an event type

curl -X DELETE https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID \
  -H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
  "https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID",
  {
    method: "DELETE",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
    },
  }
);
const data = await response.json();

Try it in the API playground →

Deletion is a soft delete -- the event type is marked as deleted but existing bookings are preserved.

Booking pages

Each organization has a public booking page at https://book.astrocal.dev/{orgSlug} that lists all eligible event types. Invitees can browse and book directly from this page without needing an API key.

Enabling an event type on the booking page

By default, event types are not shown on the booking page. To include an event type, set booking_page_enabled to true:

curl -X PATCH https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"booking_page_enabled": true}'
const response = await fetch(
  "https://api.astrocal.dev/v1/event-types/YOUR_EVENT_TYPE_ID",
  {
    method: "PATCH",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ booking_page_enabled: true }),
  }
);
const data = await response.json();

Try it in the API playground →

In the dashboard, you can toggle this under Event Types → Settings → Enable booking page.

An event type appears on the booking page only when both active is true and booking_page_enabled is true.

Error handling

StatusError CodeDescription
409conflictSlug already exists within the organization
409booking_cap_reachedBooking cap has been reached for the current period
404not_foundEvent type does not exist or is not accessible
422no_google_calendar_connectiongoogle_meet requires an active Google Calendar connection
422no_microsoft_calendar_connectionmicrosoft_teams requires an active Microsoft calendar connection

Next Steps

  • Quickstart -- Set up availability and create your first booking
  • Bookings -- Create, cancel, and reschedule bookings
  • Payments -- Add pricing to event types
  • API Reference -- Full endpoint documentation

On this page