Cal.diy 平台 API v2 Event Types 实操指南:从事件类型 CRUD 到团队调度与 Webhook 的完整配置
2026/9/10 23:46:41 网站建设 项目流程

Cal.diy 平台 API v2 Event Types 实操指南:从事件类型 CRUD 到团队调度与 Webhook 的完整配置

【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy

Cal.diy 以 "Scheduling infrastructure for absolutely everyone" 为定位,在其开放平台 API v2 中,Event Types(事件类型)是定义"可被预约的会议形态"的核心资源——时长、地点、可用规则、自定义表单均由它承载。本文以仓库内置的 event-types.md 为骨架,结合 apps/api/v2 中对应的 NestJS 控制器、输入输出 DTO、转换器与 e2e 测试,系统讲解事件类型的增删改查、8 种地点类型、6 种自定义预约字段,以及团队/组织级调度和事件类型级 Webhook 的完整接入方法。读完本文,你将能够独立调用/v2/event-types系列端点,为你的产品搭建一套可被预约的会议类型。

适用前提:文中所有路由、参数与示例均以当前仓库apps/api/v2的实现与官方参考文档为准。调用前需先阅读 authentication.md 获取 Bearer API Key;API 基础地址为https://api.cal.com/v2,所有请求都要求携带Authorization: Bearer cal_<your_api_key>(Key 以cal_live_cal_test_开头)。

一、端点总览

MethodEndpointDescription
GET/v2/event-typesList event types
POST/v2/event-typesCreate an event type
GET/v2/event-types/{eventTypeId}Get an event type
PATCH/v2/event-types/{eventTypeId}Update an event type
DELETE/v2/event-types/{eventTypeId}Delete an event type

在仓库实现中,这一组路由由两个"版本化"的 NestJS 控制器共同提供:

  • event-types_2024_06_14/controllers/event-types.controller.ts 使用@Controller({ path: "/v2/event-types", version: VERSION_2024_06_14_VALUE })承载//:eventTypeId各写读方法,并声明了cal-api-version请求头,未传正确版本值时会回退到旧版本;
  • event-types_2024_04_15/controllers/event-types.controller.ts 同时服务于VERSION_2024_04_15VERSION_2024_06_11两个旧版本。

两者都通过@UseGuards(PermissionsGuard)做全局权限拦截,并用EVENT_TYPE_READ/EVENT_TYPE_WRITE两个权限常量区分读、写操作(源码见 event-types_2024_04_15 控制器 的@Permissions([EVENT_TYPE_WRITE])用法)。每类方法对应的 e2e 测试位于 event-types.controller.e2e-spec.ts,可作为请求/响应的可运行样例。

二、列出事件类型:GET /v2/event-types

GET /v2/event-types

Query Parameters

ParameterTypeRequiredDescription
takenumberNoNumber of results (default: 10, max: 250)
skipnumberNoPagination offset

响应示例

{ "status": "success", "data": [ { "id": 123, "title": "30 Minute Meeting", "slug": "30min", "description": "A quick 30 minute call", "lengthInMinutes": 30, "locations": [ { "type": "integration", "integration": "cal-video" } ], "bookingFields": [], "disableGuests": false, "slotInterval": null, "minimumBookingNotice": 120, "beforeEventBuffer": 0, "afterEventBuffer": 0, "schedulingType": null, "metadata": {}, "requiresConfirmation": false, "price": 0, "currency": "usd", "hidden": false } ] }

响应字段速览status固定为"success"datalengthInMinutes即会议时长(分钟),slug会出现在预约 URL 中(如/{username}/30min),minimumBookingNotice表示"提前多少分钟才允许被预约"(此处 120 即最早可约 2 小时后),slotIntervalnull表示相邻可预约时段之间无强制间隔。

关于 hidden 事件类型的可见性:从控制器源码看,GET 列表会调用getResponseEventTypesWithoutHiddenFields对结果做隐藏字段过滤——也就是说公开调用时hidden: true的事件类型不会出现在列表里,只有携带所属 owner 认证信息时才被返回。此外当前 2024-06-14 版本还支持可选的sortCreatedAt查询参数("asc"/"desc")按创建顺序排序,详见控制器getEventTypes的 API 描述。

三、创建事件类型:POST /v2/event-types

POST /v2/event-types

请求体示例

{ "title": "30 Minute Meeting", "slug": "30min", "description": "A quick 30 minute call to discuss your needs", "lengthInMinutes": 30, "locations": [ { "type": "integration", "integration": "cal-video" } ], "bookingFields": [ { "type": "textarea", "name": "notes", "label": "Additional Notes", "required": false, "placeholder": "Any additional information..." } ], "disableGuests": false, "slotInterval": 15, "minimumBookingNotice": 120, "beforeEventBuffer": 5, "afterEventBuffer": 5, "scheduleId": 1, "requiresConfirmation": false, "hidden": false }

必填字段

FieldTypeDescription
titlestringDisplay name of the event type
slugstringURL-friendly identifier
lengthInMinutesnumberDuration of the event

可选字段

FieldTypeDescription
descriptionstringDescription shown on booking page
locationsarrayMeeting location options
bookingFieldsarrayCustom form fields
disableGuestsbooleanPrevent attendees from adding guests
slotIntervalnumberMinutes between available slots
minimumBookingNoticenumberMinimum minutes before booking
beforeEventBuffernumberBuffer time before event (minutes)
afterEventBuffernumberBuffer time after event (minutes)
scheduleIdnumberID of schedule to use
requiresConfirmationbooleanRequire host confirmation
hiddenbooleanHide from public profile

字段语义补充beforeEventBuffer/afterEventBuffer是"会议前后各预留的缓冲分钟数",例如 30 分钟会议前后各留 5 分钟,相邻两场约会被自动拉开到 40 分钟;scheduleId指向用户在某张"可用性排班表(Schedule)"上开放预约,关于排班表可参阅 schedules.md;price/currency用于付费事件(本示例为 0/usd)。

源码中的处理管线

创建请求并非直接落库,而是先经过inputEventTypesService.transformAndValidateCreateEventTypeInput输入转换与校验,再交给eventTypesService.createUserEventType写入,最后通过EventTypeResponseTransformPipe统一输出格式(见 2024-06-14 控制器 createEventType)。这里"转换"指把 API 友好的locationsbookingFieldsseatsrecurrence等结构翻译为内部事件类型模型——对应目录 event-types_2024_06_14/transformers/api-to-internal 下的locations.tsbooking-fields.tsrecurrence.tsseats.ts等转换器;出参方向的逆转换则在transformers/internal-to-apipipes/event-type-response.transformer.ts完成。这意味着你按下文格式传入的locationsbookingFields都是经过映射器支持的官方输入格式,字段拼写错误会在这一层被校验拦截。

四、获取单个事件类型:GET /v2/event-types/{eventTypeId}

GET /v2/event-types/{eventTypeId}

Path Parameters

ParameterTypeDescription
eventTypeIdnumberEvent type ID

授权模型(重要):该 GET 端点并非"拿到 ID 就能读",控制器注释明确列出了授权范围——系统管理员、事件类型 owner、该事件类型的 Host/被指派用户、所属团队的管理员、所属组织(含团队父组织)的管理员/owner 均可读取;而UPDATE 与 DELETE 仍仅限事件类型 owner 本人(见 getEventTypeById 的 API 描述)。服务层实现为getEventTypeByIdIfAuthorized,未授权时返回NotFoundException("Event type with id … not found")。

五、更新事件类型:PATCH /v2/event-types/{eventTypeId}

PATCH /v2/event-types/{eventTypeId}

请求体

PATCH 为局部更新语义,只需包含要修改的字段:

{ "title": "Updated Meeting Title", "lengthInMinutes": 45, "hidden": false }

该请求需EVENT_TYPE_WRITE权限;服务端先执行transformAndValidateUpdateEventTypeInput(结合当前用户与eventTypeId校验变更后的整体合法性),再调用updateEventType落库,成功响应为HTTP 200+status: "success"及更新后的事件对象。对应的集成测试覆盖见 event-types.controller.e2e-spec.ts。

六、删除事件类型:DELETE /v2/event-types/{eventTypeId}

DELETE /v2/event-types/{eventTypeId}

删除同样要求EVENT_TYPE_WRITE且仅限 owner。删除成功后响应体返回被删事件的关键信息(idlengthInMinutesslugtitle),便于客户端做本地清理:

{ "status": "success", "data": { "id": 123, "lengthInMinutes": 30, "slug": "30min", "title": "30 Minute Meeting" } }

七、地点类型(Location Types)全解

locations数组决定了"会议在哪里进行"。所有官方地点类型都以type区分,以下是文档列出的 8 种写法:

Cal Video(内置视频会议)

{ "type": "integration", "integration": "cal-video" }

Zoom

{ "type": "integration", "integration": "zoom" }

Google Meet

{ "type": "integration", "integration": "google-meet" }

Microsoft Teams

{ "type": "integration", "integration": "msteams" }

线下地址(In-Person)

{ "type": "address", "address": "123 Main St, City, Country" }

电话(Host 主动拨打)

{ "type": "userPhone" }

电话(Attendee 提供号码)

{ "type": "attendeePhone" }

自定义链接

{ "type": "link", "link": "https://custom-meeting.com/room" }

仓库中locations的转换逻辑分别位于 transformers/api-to-internal/locations.ts(API 输入 → 内部模型)与transformers/internal-to-api/locations.ts(内部模型 → API 输出),其配套单测api-to-internal.spec.ts覆盖了各类integration与自定义地点的映射,是排查"地点类型传参无效"时的第一参照。

八、自定义预约字段(Booking Fields)

bookingFields用于在预约表单上向 Attendee 收集额外信息。除示例中的placeholder外,通用字段结构为type + name + label + required;选项类字段还需options数组。

文本框(Text)

{ "type": "text", "name": "company", "label": "Company Name", "required": true, "placeholder": "Enter your company name" }

多行文本(Textarea)

{ "type": "textarea", "name": "notes", "label": "Additional Notes", "required": false }

下拉选择(Select)

{ "type": "select", "name": "topic", "label": "Meeting Topic", "required": true, "options": [ { "value": "sales", "label": "Sales Inquiry" }, { "value": "support", "label": "Support" }, { "value": "other", "label": "Other" } ] }

单选按钮(Radio)

{ "type": "radio", "name": "preference", "label": "Preferred Contact Method", "required": true, "options": [ { "value": "email", "label": "Email" }, { "value": "phone", "label": "Phone" } ] }

复选框(Checkbox)

{ "type": "checkbox", "name": "terms", "label": "I agree to the terms", "required": true }

电话号码(Phone)

{ "type": "phone", "name": "phone", "label": "Phone Number", "required": false }

提示:optionsvalue是提交给 API 的实际取值,label是展示文案;required决定该字段是否阻塞预约提交。若需要在预约后把这些字段转发到下游系统,可在事件类型级 Webhook 中接收携带字段值的事件(见下文"事件类型 Webhooks")。字段结构的双向转换由 booking-fields.ts 等转换器实现,提交前会对字段类型与选项做校验。

九、团队事件类型(Team Event Types)

团队场景请使用team-scoped端点,资源归属校验会落到团队维度:

列出团队事件类型

GET /v2/teams/{teamId}/event-types

创建团队事件类型

POST /v2/teams/{teamId}/event-types

团队事件类型在基础必填字段之上,额外支持schedulingTypehosts

{ "title": "Team Meeting", "slug": "team-meeting", "lengthInMinutes": 30, "schedulingType": "ROUND_ROBIN", "hosts": [ { "userId": 1, "isFixed": false }, { "userId": 2, "isFixed": false } ] }

hosts声明可承接预约的团队成员;isFixed: false表示非固定 Host,可参与轮转分配。

调度类型(Scheduling Types)

TypeDescription
ROUND_ROBINDistributes bookings among team members
COLLECTIVEAll team members must attend
MANAGEDParent event type that creates child event types

调度类型的取值在仓库中有枚举定义可对照:scheduling-type.ts(ROUND_ROBIN = "ROUND_ROBIN"COLLECTIVE = "COLLECTIVE"MANAGED = "MANAGED")。团队事件类型的服务层与出参序列化位于 modules/teams/event-types/services/teams-event-types.service.ts 与 output-team-event-types-response.pipe.ts;值得注意的是,即使是单机/v2/event-types/:eventTypeId的 GET,一旦发现事件归属团队(带teamId),控制器也会切换走团队专用输出管道来格式化响应。

十、私有链接(Private Links)

hidden事件类型用于"不公开但可预约";而Private Link更进一步:为事件生成绕过公开页可见性的专属预约链接,适合发给特定客户。

列出私有链接

GET /v2/event-types/{eventTypeId}/private-links

创建私有链接

POST /v2/event-types/{eventTypeId}/private-links

仓库中该功能的控制器路径为/v2/event-types/:eventTypeId/private-links(见 event-types-private-links.controller.ts),并比文档更进一步地实现了PATCH /private-links/{linkId}(更新)DELETE /private-links/{linkId}(删除)两个管理端点。所有操作都叠加了ApiAuthGuard + EventTypeOwnershipGuard双重校验——即使拿到eventTypeId,非 owner 也无法读写其私有链接。完整的链路(创建→列表→更新→删除)在 event-types-private-links.controller.e2e-spec.ts 中有端到端覆盖。

十一、组织级(Organization)事件类型

当事件类型归属于某个组织下的团队时,可走组织范围的 team-scoped 端点。先定位组织下的团队:

列出组织团队

GET /v2/organizations/{orgId}/teams

响应示例

{ "status": "success", "data": [ { "id": 1, "name": "Sales Team", "slug": "sales", "parentId": null, "isOrganization": false } ] }

isOrganization用于区分"组织根节点"与"普通团队",parentId指向父组织/父团队。

组织范围获取团队事件类型

GET /v2/organizations/{orgId}/teams/{teamId}/event-types

组织范围创建团队事件类型

POST /v2/organizations/{orgId}/teams/{teamId}/event-types

组织下的团队事件类型同样支持三种调度模式,语义如下:

ModeDescription
COLLECTIVEAll team members must attend the meeting
ROUND_ROBINDistributes bookings among team members based on availability and priority
MANAGEDParent event type that creates child event types for team members

三者差异:COLLECTIVE要求所有成员同时出现在同一场会议;ROUND_ROBIN依据可用性(availability)与优先级把预约轮流分发给成员;MANAGED是"父事件类型",由平台为每个成员自动派生"子事件类型",适合统一管理一个团队的对外服务目录。

十二、事件类型级 Webhooks

除了全局 Webhook,还可以把 Webhook 绑定到单个事件类型,只在它被预约/取消等时刻收到通知:

列出事件类型 Webhooks

GET /v2/event-types/{eventTypeId}/webhooks

创建事件类型 Webhook

POST /v2/event-types/{eventTypeId}/webhooks
{ "subscriberUrl": "https://your-app.com/webhook", "triggers": ["BOOKING_CREATED"], "active": true }
  • subscriberUrl:接收通知的 HTTPS 回调地址;
  • triggers:触发事件数组,BOOKING_CREATED表示"预约创建时"推送(完整的可用触发器清单与负载格式可查阅 webhooks.md);
  • active:是否启用该订阅。

仓库实现位于 event-types-webhooks.controller.ts,控制器路径为/v2/event-types/:eventTypeId/webhooks,通过IsUserEventTypeWebhookGuard校验"当前用户确实是该事件类型 owner";请求体会经WebhookInputPipe清洗后写入,e2e 测试见同目录的event-types-webhooks.controller.e2e-spec.ts

十三、从零开始的接入流程(Checklist)

结合 calcom-api/SKILL.md 的通用工作流,一次典型接入可以这样走:

  1. 鉴权准备:生成 API Key(cal_live_/cal_test_),请求头携带Authorization: Bearer cal_<key>;2024-06-14 版端点还要求cal-api-version头,详见 authentication.md。
  2. 确认排班:先通过GET /v2/schedules(参考 schedules.md)拿到scheduleId,再创建事件类型并绑定。
  3. 创建事件类型POST /v2/event-types,至少给出title+slug+lengthInMinutes;按需组装locationsbookingFields、缓冲区与确认策略。
  4. 公开预约链路:用返回的idslug生成 booking URL;对外可只暴露hidden/private-link 形态的入口。
  5. 团队化(可选):通过/v2/teams/{teamId}/event-types或组织范围端点创建,选择ROUND_ROBIN/COLLECTIVE/MANAGED并配置hosts
  6. 订阅通知POST /v2/event-types/{eventTypeId}/webhooks注册回调;收到事件后可用文档 webhooks.md 中说明的签名头做验签。

关键实现文件索引

想深入代码层面验证以上行为,可优先查看:

  • 2024-06-14 主控制器:apps/api/v2/src/platform/event-types/event-types_2024_06_14/controllers/event-types.controller.ts
  • 旧版本(2024-04-15 / 2024-06-11)控制器与其公开端点:event-types_2024_04_15/controllers/event-types.controller.ts
  • 调度类型枚举:scheduling-type.ts
  • 双向字段转换器目录:transformers/api-to-internal 与transformers/internal-to-api
  • 私有链接控制器:event-types-private-links.controller.ts
  • 团队事件类型服务:modules/teams/event-types/services/teams-event-types.service.ts
  • 事件类型级 Webhook 控制器:modules/event-types/controllers/event-types-webhooks.controller.ts

以上文件与文档共同构成了"文档描述 → 控制器路由 → DTO 校验 → 转换器映射 → 服务层落库"的完整证据链,可放心作为你接入或二次开发 Event Types 功能时的依据。

【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询