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
- An Astrocal account with an API key (Authentication guide)
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
| Field | Type | Description |
|---|---|---|
title | string | Display name (1-255 characters) |
slug | string | URL-safe identifier (lowercase, hyphens only) |
duration_minutes | number | Meeting length in minutes (5-480) |
Optional fields
| Field | Type | Default | Description |
|---|---|---|---|
description | string | null | Event type description (max 1000 chars) |
buffer_before_minutes | number | 0 | Buffer time before meetings (0-120 minutes) |
buffer_after_minutes | number | 0 | Buffer time after meetings (0-120 minutes) |
color | string | "#3b82f6" | Hex color for calendar display |
max_attendees | number | 1 | Maximum attendees per slot (1-100). Values > 1 enable group bookings. |
duration_options | number[] | null | Available durations for variable-length bookings (e.g., [15, 30, 60]) |
price_amount | number|null | null | Price in cents (min 100). See Payments |
price_currency | string | "usd" | 3-letter ISO currency code |
reminder_intervals | number[] | [1440, 60] | Reminder email intervals in minutes before the meeting |
booking_cap | number|null | null | Maximum bookings allowed per period (1-10000) |
booking_cap_period | string|null | null | Cap period: "daily", "weekly", "monthly", or "total" |
booking_page_enabled | boolean | false | Show this event type on your organization's public booking page |
conferencing_provider | string|null | null | "zoom", "google_meet", "microsoft_teams", "custom", or "in_person". See Conferencing |
custom_meeting_url | string|null | null | Static meeting URL — required when conferencing_provider is "custom" |
location | string|null | null | Physical 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
| Period | Description |
|---|---|
daily | Resets at midnight in the event type's timezone |
weekly | Resets at the start of each week (Monday) in the event type's timezone |
monthly | Resets at the start of each month in the event type's timezone |
total | Never resets -- counts all bookings since the cap was set |
How caps are enforced
-
Availability layer: When a cap is reached, the availability endpoint returns an empty
slotsarray and acapped: trueflag. This lets your UI show that the event type is fully booked for the period. -
Booking layer: If a booking is attempted after the cap is reached (e.g., due to a race condition), the API returns a
409 Conflictwith error codebooking_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.
| Provider | Behaviour | Prerequisite |
|---|---|---|
zoom | A unique Zoom meeting is created for each booking | Zoom connected in the dashboard |
google_meet | A unique Google Meet link is generated on the calendar event for each booking | An active Google Calendar connection |
microsoft_teams | A unique Microsoft Teams link is generated on the calendar event for each booking | An active Microsoft calendar connection |
custom | The static custom_meeting_url is reused for every booking | custom_meeting_url set |
in_person | No meeting link; the location address is attached to bookings instead | location 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
422with error codeno_google_calendar_connection. - For paid bookings, the Meet link is generated after payment succeeds (while the booking is
pending_payment,meeting_urlisnull). - 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
422with error codeno_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_urlisnull). - 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
| Status | Error Code | Description |
|---|---|---|
| 409 | conflict | Slug already exists within the organization |
| 409 | booking_cap_reached | Booking cap has been reached for the current period |
| 404 | not_found | Event type does not exist or is not accessible |
| 422 | no_google_calendar_connection | google_meet requires an active Google Calendar connection |
| 422 | no_microsoft_calendar_connection | microsoft_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