docs/api-reference/v2/v1-v2-differences.mdx
The v2 API includes numerous enhancements and new features that are not available in v1:
In v1, you authenticated using an API key passed as a query parameter:
curl https://api.cal.com/v1/bookings?apiKey=cal_test_xxxxxx
In v2, you authenticate using an API key in the Authorization header:
curl https://api.cal.com/v2/bookings \
-H "Authorization: Bearer cal_test_xxxxxx" \
-H "cal-api-version: 2024-08-13"
V1 endpoint:
POST /v1/bookings
V1 request body:
{
"eventTypeId": 123,
"start": "2023-05-24T13:00:00.000Z",
"end": "2023-05-24T13:30:00.000Z",
"responses": {
"name": "John Doe",
"email": "[email protected]",
"location": {
"value": "userPhone",
"optionValue": ""
}
},
"timeZone": "Europe/London",
"language": "en",
"metadata": {}
}
V2 endpoint:
POST /v2/bookings
V2 request body:
{
"eventTypeId": 123,
"start": "2024-08-13T09:00:00Z",
"attendee": {
"name": "John Doe",
"email": "[email protected]",
"timeZone": "America/New_York",
"language": "en"
},
"location": {
"type": "phone"
},
"metadata": {}
}
Key differences:
responses object replaced with attendee objectend time is no longer required (calculated from event type duration)location structure changed from {value, optionValue} to {type}cal-api-version: 2024-08-13 headerstatus and data wrapperV1 response:
{
"booking": {
"id": 91,
"uid": "bFJeNb2uX8ANpT3JL5EfXw",
"startTime": "2023-05-25T09:30:00.000Z",
"endTime": "2023-05-25T10:30:00.000Z",
"attendees": [...],
"status": "ACCEPTED"
}
}
V2 response:
{
"status": "success",
"data": {
"id": 123,
"uid": "booking_uid_123",
"start": "2024-08-13T15:30:00Z",
"end": "2024-08-13T16:30:00Z",
"duration": 60,
"status": "accepted",
"hosts": [...],
"attendees": [...],
"eventType": {
"id": 1,
"slug": "some-event"
}
}
}
V1: Not available as a dedicated endpoint
V2 endpoint:
GET /v2/bookings
V2 query parameters:
status: Filter by booking status (accepted, pending, cancelled, rejected)attendeeEmail: Filter by attendee emaileventTypeId: Filter by event typeafterStart, beforeEnd: Filter by date rangetake, skip: PaginationsortStart, sortEnd, sortCreated: Sorting optionsV2 response includes pagination:
{
"status": "success",
"data": [...],
"pagination": {
"totalItems": 123,
"currentPage": 2,
"totalPages": 13,
"hasNextPage": true
}
}
V1 endpoint:
DELETE /v1/bookings?id={id}&allRemainingBookings=false&cancellationReason=reason
V2 endpoint:
POST /v2/bookings/{uid}/cancel
V2 request body:
{
"cancellationReason": "User requested cancellation"
}
Key differences:
uid in path instead of id query parameterV1: Required creating a new booking with rescheduleUid
V2 endpoint:
POST /v2/bookings/{uid}/reschedule
V2 request body:
{
"start": "2024-08-14T10:00:00Z",
"reschedulingReason": "Conflict with another meeting"
}
Key differences:
V1 endpoint:
GET /v1/event-types
V2 endpoint:
GET /v2/event-types
Key differences:
{status, data} structureV1 endpoint:
POST /v1/event-types
V1 request body:
{
"title": "30 Min Meeting",
"slug": "30min",
"length": 30,
"locations": [{"type": "integrations:zoom"}]
}
V2 endpoint:
POST /v2/event-types
V2 request body:
{
"title": "30 Min Meeting",
"slug": "30min",
"lengthInMinutes": 30,
"locations": [{"type": "zoom"}]
}
Key differences:
length renamed to lengthInMinutes for clarityintegrations: prefix)V1 endpoint:
PATCH /v1/event-types/{id}
V2 endpoint:
PATCH /v2/event-types/{id}
Key differences:
V1 endpoint:
GET /v1/schedules
V2 endpoint:
GET /v2/schedules
Key differences:
V1 endpoint:
POST /v1/schedules
V1 request body:
{
"name": "Working Hours",
"timeZone": "America/New_York",
"availability": [...]
}
V2 endpoint:
POST /v2/schedules
V2 request body:
{
"name": "Working Hours",
"timeZone": "America/New_York",
"isDefault": false,
"schedule": [...]
}
Key differences:
availability renamed to schedule in v2isDefault flag added to set default scheduleV1 endpoint:
GET /v1/webhooks
V2 endpoint:
GET /v2/webhooks
V1 endpoint:
POST /v1/webhooks
V1 request body:
{
"subscriberUrl": "https://example.com/webhook",
"eventTriggers": ["BOOKING_CREATED"],
"active": true
}
V2 endpoint:
POST /v2/webhooks
V2 request body:
{
"payloadTemplate": null,
"triggers": ["BOOKING_CREATED"],
"subscriberUrl": "https://example.com/webhook",
"active": true
}
Key differences:
eventTriggers renamed to triggerspayloadTemplate for custom webhook payloadsV1 endpoint:
GET /v1/slots/available?eventTypeId=123&startTime=2024-01-01&endTime=2024-01-31
V2 endpoint:
GET /v2/slots/available?eventTypeId=123&startTime=2024-01-01T00:00:00Z&endTime=2024-01-31T23:59:59Z
Key differences:
V1 endpoint:
GET /v1/teams
V2 endpoint:
GET /v2/teams
Key differences:
V1 endpoint:
GET /v1/users/{id}
V2 endpoint:
GET /v2/me
Key differences:
/me endpoint for current user{
"booking": {...}
}
{
"status": "success",
"data": {...}
}
All v2 responses follow this consistent structure with:
status: Either "success" or "error"data: The actual response dataerror: Error details (only present when status is "error"){
"message": "Event type not found"
}
{
"status": "error",
"error": {
"code": "NOT_FOUND",
"message": "Event type not found"
}
}
V2 provides more structured error responses with error codes for better error handling.
Authorization header instead of query parametercal-api-version: 2024-08-13 header to all requests/v1/ to /v2/responses → attendee for bookings){status, data} wrapperlength → lengthInMinutes, eventTriggers → triggers, etc.)If you have questions or need assistance with migrating to v2, please contact our support team.