feat: v2 return slots based on routing form response (#20637)

* feat: routing form response slots

* fix

* test routing form slots

* chore: bump libraries

* docs

* docs

* chore: api docs from main to merge main

* chore: regenerate api docs
This commit is contained in:
Lauris Skraucis
2025-04-16 10:55:01 +03:00
committed by GitHub
parent 43a6663230
commit 15d2c0f858
13 changed files with 6532 additions and 867 deletions
+1 -1
View File
@@ -38,7 +38,7 @@
"@axiomhq/winston": "^1.2.0",
"@calcom/platform-constants": "*",
"@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.172",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.173",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
@@ -8,6 +8,7 @@ import { MeModule } from "@/ee/me/me.module";
import { ProviderModule } from "@/ee/provider/provider.module";
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
import { RoutingFormsModule } from "@/modules/routing-forms/routing-forms.module";
import { SlotsModule_2024_04_15 } from "@/modules/slots/slots-2024-04-15/slots.module";
import { SlotsModule_2024_09_04 } from "@/modules/slots/slots-2024-09-04/slots.module";
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
@@ -33,6 +34,7 @@ import { Module } from "@nestjs/common";
SlotsModule_2024_04_15,
SlotsModule_2024_09_04,
TeamsModule,
RoutingFormsModule,
],
})
export class PlatformEndpointsModule implements NestModule {
@@ -40,7 +40,7 @@ const SCALE = "SCALE";
version: API_VERSIONS_VALUES,
})
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, PlatformPlanGuard, IsAdminAPIEnabledGuard)
@DocsTags("Orgs / Orgs")
@DocsTags("Managed Orgs")
@ApiHeader(X_CAL_CLIENT_ID_HEADER)
@ApiHeader(X_CAL_SECRET_KEY_HEADER)
export class OrganizationsOrganizationsController {
@@ -0,0 +1,44 @@
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { API_KEY_HEADER } from "@/lib/docs/headers";
import { ResponseSlotsOutput } from "@/modules/routing-forms/outputs/response-slots.output";
import { RoutingFormsService } from "@/modules/routing-forms/services/routing-forms.service";
import { Controller, HttpCode, HttpStatus, Param, Post, Query, Req } from "@nestjs/common";
import { ApiHeader, ApiOperation, ApiTags } from "@nestjs/swagger";
import { Request } from "express";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { GetAvailableSlotsInput_2024_09_04 } from "@calcom/platform-types";
@Controller({
path: "/v2/routing-forms/:routingFormId",
version: API_VERSIONS_VALUES,
})
@ApiTags("Routing forms")
@ApiHeader(API_KEY_HEADER)
export class RoutingFormsController {
constructor(private readonly routingFormsService: RoutingFormsService) {}
@Post("/calculate-slots")
@ApiOperation({
summary: "Calculate slots based on routing form response",
description:
"It will not actually save the response just return the routed event type and slots when it can be booked.",
})
@HttpCode(HttpStatus.OK)
async calculateSlotsBasedOnRoutingFormResponse(
@Req() request: Request,
@Query() query: GetAvailableSlotsInput_2024_09_04,
@Param("routingFormId") routingFormId: string
): Promise<ResponseSlotsOutput> {
const responseSlots = await this.routingFormsService.calculateSlotsBasedOnRoutingFormResponse(
request,
routingFormId,
query
);
return {
status: SUCCESS_STATUS,
data: responseSlots,
};
}
}
@@ -0,0 +1,355 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import {
ResponseSlotsOutput,
ResponseSlotsOutputData,
} from "@/modules/routing-forms/outputs/response-slots.output";
import { SlotsModule_2024_09_04 } from "@/modules/slots/slots-2024-09-04/slots.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import { App_RoutingForms_Form, EventType, User } from "@prisma/client";
import * as request from "supertest";
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
import { RoutingFormsRepositoryFixture } from "test/fixtures/repository/routing-forms.repository.fixture";
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { randomString } from "test/utils/randomString";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { Team } from "@calcom/prisma/client";
describe("Routing forms endpoints", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let teamRepositoryFixture: TeamRepositoryFixture;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
let membershipsRepositoryFixture: MembershipRepositoryFixture;
let routingFormsRepositoryFixture: RoutingFormsRepositoryFixture;
const teammateEmailTwo = `routing-forms-teammate-1-${randomString()}`;
const teammateEmailOne = `routing-forms-teammate-2-${randomString()}`;
let team: Team;
let teammateOne: User;
let teammateTwo: User;
let collectiveEventTypeFootball: EventType;
let collectiveEventTypeBasketball: EventType;
let routingForm: App_RoutingForms_Form;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
AppModule,
PrismaModule,
UsersModule,
TokensModule,
SchedulesModule_2024_06_11,
SlotsModule_2024_09_04,
],
})
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
.compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
routingFormsRepositoryFixture = new RoutingFormsRepositoryFixture(moduleRef);
teammateOne = await userRepositoryFixture.create({
email: teammateEmailOne,
name: teammateEmailOne,
username: teammateEmailOne,
});
teammateTwo = await userRepositoryFixture.create({
email: teammateEmailTwo,
name: teammateEmailTwo,
username: teammateEmailTwo,
});
const teamSlug = `routing-forms-team-${randomString()}`;
team = await teamRepositoryFixture.create({
name: teamSlug,
slug: teamSlug,
isOrganization: false,
});
await membershipsRepositoryFixture.create({
role: "MEMBER",
user: { connect: { id: teammateOne.id } },
team: { connect: { id: team.id } },
accepted: true,
});
await membershipsRepositoryFixture.create({
role: "MEMBER",
user: { connect: { id: teammateTwo.id } },
team: { connect: { id: team.id } },
accepted: true,
});
const collectiveEventTypeFootballSlug = `routing-forms-collective-event-type-football-${randomString()}`;
collectiveEventTypeFootball = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "COLLECTIVE",
team: {
connect: { id: team.id },
},
title: "Collective Event Type Football",
slug: collectiveEventTypeFootballSlug,
length: 60,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
schedule: {
create: {
name: "football",
timeZone: "Europe/London",
user: { connect: { id: teammateOne.id } },
availability: {
create: {
days: [1],
startTime: new Date("1970-01-01T09:00:00Z"),
endTime: new Date("1970-01-01T11:00:00Z"),
},
},
},
},
users: {
connect: [{ id: teammateOne.id }, { id: teammateTwo.id }],
},
hosts: {
create: [
{
userId: teammateOne.id,
isFixed: true,
},
{
userId: teammateTwo.id,
isFixed: true,
},
],
},
});
const collectiveEventTypeBasketballSlug = `routing-forms-collective-event-type-basketball-${randomString()}`;
collectiveEventTypeBasketball = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "COLLECTIVE",
team: {
connect: { id: team.id },
},
title: "Collective Event Type Basketball",
slug: collectiveEventTypeBasketballSlug,
length: 60,
assignAllTeamMembers: true,
bookingFields: [],
locations: [],
schedule: {
create: {
name: "basketball",
timeZone: "Europe/London",
user: { connect: { id: teammateTwo.id } },
availability: {
create: {
days: [2],
startTime: new Date("1970-01-01T11:00:00Z"),
endTime: new Date("1970-01-01T13:00:00Z"),
},
},
},
},
users: {
connect: [{ id: teammateOne.id }, { id: teammateTwo.id }],
},
hosts: {
create: [
{
userId: teammateOne.id,
isFixed: true,
},
{
userId: teammateTwo.id,
isFixed: true,
},
],
},
});
routingForm = await routingFormsRepositoryFixture.create({
name: "football or basketball",
description: "Do you want to play football or basketball?",
routes: [
{
id: "baba89aa-4567-489a-bcde-f1961b1ae10c",
action: {
type: "eventTypeRedirectUrl",
value: `team/${team.slug}/${collectiveEventTypeFootballSlug}`,
eventTypeId: collectiveEventTypeFootball.id,
},
queryValue: {
id: "a8a8b9aa-0123-4456-b89a-b1961b1ae10c",
type: "group",
children1: {
"99ab8aaa-4567-489a-bcde-f1961b1ae5cd": {
type: "rule",
properties: {
field: "dcd8b978-fa11-47be-801a-5ca2cd4ec16e",
value: ["755c11d1-fa46-4d03-8e15-d2c4ab3407d7"],
operator: "select_equals",
valueSrc: ["value"],
valueType: ["select"],
valueError: [null],
},
},
},
},
attributesQueryValue: { id: "b8998aab-cdef-4012-b456-71961b1ae10c", type: "group" },
attributeRoutingConfig: {},
fallbackAttributesQueryValue: { id: "ab98888a-89ab-4cde-b012-31961b1ae10c", type: "group" },
},
{
id: "88a98b98-0123-4456-b89a-b1961b1b1a04",
action: {
type: "eventTypeRedirectUrl",
value: `team/${team.slug}/${collectiveEventTypeBasketballSlug}`,
eventTypeId: collectiveEventTypeBasketball.id,
},
queryValue: {
id: "bab8b9a8-cdef-4012-b456-71961b1b1a04",
type: "group",
children1: {
"9988a98a-0123-4456-b89a-b1961b1b1f8b": {
type: "rule",
properties: {
field: "dcd8b978-fa11-47be-801a-5ca2cd4ec16e",
value: ["4cbb1537-0ab9-4286-b4bf-08b08ff23372"],
operator: "select_equals",
valueSrc: ["value"],
valueType: ["select"],
valueError: [null],
},
},
},
},
attributesQueryValue: { id: "99a89a9b-89ab-4cde-b012-31961b1b1a04", type: "group" },
attributeRoutingConfig: {},
fallbackAttributesQueryValue: { id: "88989a98-4567-489a-bcde-f1961b1b1a04", type: "group" },
},
{
id: "9b9ab9bb-cdef-4012-b456-71961b1a8b78",
action: {
type: "customPageMessage",
value: "Thank you for your interest! We will be in touch soon.",
},
isFallback: true,
queryValue: { id: "9b9ab9bb-cdef-4012-b456-71961b1a8b78", type: "group" },
attributesQueryValue: { id: "88b988ab-cdef-4012-b456-71961b1ad81a", type: "group" },
fallbackAttributesQueryValue: { id: "a8b89a9a-89ab-4cde-b012-31961b1ad81a", type: "group" },
},
],
createdAt: new Date("2025-04-09T15:10:46.651Z"),
updatedAt: new Date("2025-04-09T15:11:37.696Z"),
fields: [
{
id: "dcd8b978-fa11-47be-801a-5ca2cd4ec16e",
type: "select",
label: "sport",
options: [
{ id: "755c11d1-fa46-4d03-8e15-d2c4ab3407d7", label: "football" },
{ id: "4cbb1537-0ab9-4286-b4bf-08b08ff23372", label: "basketball" },
],
required: true,
},
],
user: {
connect: { id: teammateOne.id },
},
disabled: false,
settings: { emailOwnerOnSubmission: true },
team: {
connect: { id: team.id },
},
position: 0,
});
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
await app.init();
});
describe("Calculate slots", () => {
const slotsQuery = "start=2060-01-12&end=2060-01-16";
it("should return correct slots for option 1", async () => {
const body = {
sport: "football",
};
const reserveResponse = await request(app.getHttpServer())
.post(`/v2/routing-forms/${routingForm.id}/calculate-slots?${slotsQuery}`)
.send(body)
.expect(200);
const responseBody: ResponseSlotsOutput = reserveResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const responseBodyData: ResponseSlotsOutputData = responseBody.data;
expect(responseBodyData.eventTypeId).toEqual(collectiveEventTypeFootball.id);
expect(responseBodyData.slots).toEqual({
"2060-01-12": [
{
start: "2060-01-12T09:00:00.000Z",
},
{
start: "2060-01-12T10:00:00.000Z",
},
],
});
});
it("should return correct slots for option 2", async () => {
const body = {
sport: "basketball",
};
const reserveResponse = await request(app.getHttpServer())
.post(`/v2/routing-forms/${routingForm.id}/calculate-slots?${slotsQuery}`)
.send(body)
.expect(200);
const responseBody: ResponseSlotsOutput = reserveResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const responseBodyData: ResponseSlotsOutputData = responseBody.data;
expect(responseBodyData.eventTypeId).toEqual(collectiveEventTypeBasketball.id);
expect(responseBodyData.slots).toEqual({
"2060-01-13": [
{
start: "2060-01-13T11:00:00.000Z",
},
{
start: "2060-01-13T12:00:00.000Z",
},
],
});
});
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(teammateOne.email);
await userRepositoryFixture.deleteByEmail(teammateTwo.email);
await teamRepositoryFixture.delete(team.id);
await app.close();
});
});
@@ -0,0 +1,32 @@
import { ApiExtraModels, ApiProperty, getSchemaPath } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsNumber, ValidateNested } from "class-validator";
import { ApiResponseWithoutData, SlotsOutput_2024_09_04 } from "@calcom/platform-types";
import { RangeSlotsOutput_2024_09_04 } from "@calcom/platform-types";
@ApiExtraModels(SlotsOutput_2024_09_04, RangeSlotsOutput_2024_09_04)
export class ResponseSlotsOutputData {
@IsNumber()
@ApiProperty()
eventTypeId!: number;
@ValidateNested()
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(SlotsOutput_2024_09_04) },
{ $ref: getSchemaPath(RangeSlotsOutput_2024_09_04) },
],
})
@Type(() => Object)
slots!: SlotsOutput_2024_09_04 | RangeSlotsOutput_2024_09_04;
}
export class ResponseSlotsOutput extends ApiResponseWithoutData {
@ValidateNested()
@ApiProperty({
type: ResponseSlotsOutputData,
})
@Type(() => ResponseSlotsOutputData)
data!: ResponseSlotsOutputData;
}
@@ -1,10 +1,17 @@
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RoutingFormsController } from "@/modules/routing-forms/controllers/routing-forms.controller";
import { RoutingFormsRepository } from "@/modules/routing-forms/routing-forms.repository";
import { RoutingFormsService } from "@/modules/routing-forms/services/routing-forms.service";
import { SlotsModule_2024_09_04 } from "@/modules/slots/slots-2024-09-04/slots.module";
import { StripeModule } from "@/modules/stripe/stripe.module";
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule],
providers: [RoutingFormsRepository],
imports: [PrismaModule, TeamsEventTypesModule, SlotsModule_2024_09_04, StripeModule],
providers: [RoutingFormsRepository, RoutingFormsService, OrganizationsRepository],
controllers: [RoutingFormsController],
exports: [RoutingFormsRepository],
})
export class RoutingFormsModule {}
@@ -0,0 +1,95 @@
import { SlotsService_2024_09_04 } from "@/modules/slots/slots-2024-09-04/services/slots.service";
import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-event-types.repository";
import { Injectable, NotFoundException } from "@nestjs/common";
import { Request } from "express";
import { getRoutedUrl } from "@calcom/platform-libraries";
import { GetAvailableSlotsInput_2024_09_04 } from "@calcom/platform-types";
@Injectable()
export class RoutingFormsService {
constructor(
private readonly teamsEventTypesRepository: TeamsEventTypesRepository,
private readonly slotsService: SlotsService_2024_09_04
) {}
async calculateSlotsBasedOnRoutingFormResponse(
request: Request,
formId: string,
slotsQuery: GetAvailableSlotsInput_2024_09_04
) {
const eventTypeId = await this.getRoutedEventTypeId(request, formId);
if (!eventTypeId) {
throw new NotFoundException("Event type not found.");
}
const slots = await this.slotsService.getAvailableSlots({
eventTypeId,
...slotsQuery,
});
return {
eventTypeId,
slots,
};
}
private async getRoutedEventTypeId(request: Request, formId: string) {
const routingUrl = await this.getRoutingUrl(request, formId);
if (!this.isEventTypeRedirectUrl(routingUrl)) {
throw new NotFoundException("Routed to a non cal.com event type URL.");
}
const { teamId, eventTypeSlug } = this.extractTeamIdAndEventTypeSlugFromRedirectUrl(routingUrl);
const eventType = await this.teamsEventTypesRepository.getEventTypeByTeamIdAndSlug(teamId, eventTypeSlug);
return eventType?.id;
}
private async getRoutingUrl(request: Request, formId: string) {
const params = Object.fromEntries(new URLSearchParams(request.body));
const routedUrlData = await getRoutedUrl({
req: request,
query: { ...params, "cal.isBookingDryRun": "true", form: formId },
});
const destination = routedUrlData?.redirect?.destination;
if (!destination) {
throw new NotFoundException("Route to which the form response should be redirected not found.");
}
return new URL(destination);
}
private extractTeamIdAndEventTypeSlugFromRedirectUrl(routingUrl: URL) {
const eventTypeSlug = this.extractEventTypeFromRoutedUrl(routingUrl);
const teamId = this.extractTeamIdFromRoutedUrl(routingUrl);
if (!teamId) {
throw new NotFoundException("Team ID not found in the routed URL.");
}
if (!eventTypeSlug) {
throw new NotFoundException("Event type slug not found in the routed URL.");
}
return { teamId, eventTypeSlug };
}
private isEventTypeRedirectUrl(routingUrl: URL) {
const routingSearchParams = routingUrl.searchParams;
return routingSearchParams.get("cal.action") === "eventTypeRedirectUrl";
}
private extractTeamIdFromRoutedUrl(routingUrl: URL) {
const routingSearchParams = routingUrl.searchParams;
return Number(routingSearchParams.get("cal.teamId"));
}
private extractEventTypeFromRoutedUrl(routingUrl: URL) {
const pathNameParams = routingUrl.pathname.split("/");
return pathNameParams[pathNameParams.length - 1];
}
}
+541 -396
View File
@@ -2019,278 +2019,6 @@
]
}
},
"/v2/organizations/{orgId}/organizations": {
"post": {
"operationId": "OrganizationsOrganizationsController_createOrganization",
"summary": "Create an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateOrganizationInput"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateManagedOrganizationOutput"
}
}
}
}
},
"tags": [
"Orgs / Orgs"
]
},
"get": {
"operationId": "OrganizationsOrganizationsController_getOrganizations",
"summary": "Get all organizations within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationsOutput"
}
}
}
}
},
"tags": [
"Orgs / Orgs"
]
}
},
"/v2/organizations/{orgId}/organizations/{managedOrganizationId}": {
"get": {
"operationId": "OrganizationsOrganizationsController_getOrganization",
"summary": "Get an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": [
"Orgs / Orgs"
]
},
"patch": {
"operationId": "OrganizationsOrganizationsController_updateOrganization",
"summary": "Update an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganizationInput"
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": [
"Orgs / Orgs"
]
},
"delete": {
"operationId": "OrganizationsOrganizationsController_deleteOrganization",
"summary": "Delete an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": [
"Orgs / Orgs"
]
}
},
"/v2/organizations/{orgId}/routing-forms": {
"get": {
"operationId": "OrganizationsRoutingFormsController_getOrganizationRoutingForms",
@@ -7096,7 +6824,7 @@
"/v2/bookings/{bookingUid}/reassign": {
"post": {
"operationId": "BookingsController_2024_08_13_reassignBooking",
"summary": "Reassign booking to auto-selected host",
"summary": "Reassign a booking to auto-selected host",
"description": "The provided authorization header refers to the owner of the booking.",
"parameters": [
{
@@ -8683,6 +8411,278 @@
]
}
},
"/v2/organizations/{orgId}/organizations": {
"post": {
"operationId": "OrganizationsOrganizationsController_createOrganization",
"summary": "Create an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateOrganizationInput"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateManagedOrganizationOutput"
}
}
}
}
},
"tags": [
"Managed Orgs"
]
},
"get": {
"operationId": "OrganizationsOrganizationsController_getOrganizations",
"summary": "Get all organizations within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationsOutput"
}
}
}
}
},
"tags": [
"Managed Orgs"
]
}
},
"/v2/organizations/{orgId}/organizations/{managedOrganizationId}": {
"get": {
"operationId": "OrganizationsOrganizationsController_getOrganization",
"summary": "Get an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": [
"Managed Orgs"
]
},
"patch": {
"operationId": "OrganizationsOrganizationsController_updateOrganization",
"summary": "Update an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganizationInput"
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": [
"Managed Orgs"
]
},
"delete": {
"operationId": "OrganizationsOrganizationsController_deleteOrganization",
"summary": "Delete an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": [
"Managed Orgs"
]
}
},
"/v2/me": {
"get": {
"operationId": "MeController_getMe",
@@ -8953,6 +8953,101 @@
]
}
},
"/v2/routing-forms/{routingFormId}/calculate-slots": {
"post": {
"operationId": "RoutingFormsController_calculateSlotsBasedOnRoutingFormResponse",
"summary": "Calculate slots based on routing form response",
"description": "It will not actually save the response just return the routed event type and slots when it can be booked.",
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "start",
"required": true,
"in": "query",
"description": "\n Time starting from which available slots should be checked.\n \n Must be in UTC timezone as ISO 8601 datestring.\n \n You can pass date without hours which defaults to start of day or specify hours:\n 2024-08-13 (will have hours 00:00:00 aka at very beginning of the date) or you can specify hours manually like 2024-08-13T09:00:00Z\n ",
"example": "2050-09-05",
"schema": {
"type": "string"
}
},
{
"name": "end",
"required": true,
"in": "query",
"description": "\n Time until which available slots should be checked.\n \n Must be in UTC timezone as ISO 8601 datestring.\n \n You can pass date without hours which defaults to end of day or specify hours:\n 2024-08-20 (will have hours 23:59:59 aka at the very end of the date) or you can specify hours manually like 2024-08-20T18:00:00Z",
"example": "2050-09-06",
"schema": {
"type": "string"
}
},
{
"name": "timeZone",
"required": false,
"in": "query",
"description": "Time zone in which the available slots should be returned. Defaults to UTC.",
"example": "Europe/Rome",
"schema": {
"type": "string"
}
},
{
"name": "duration",
"required": false,
"in": "query",
"description": "If event type has multiple possible durations then you can specify the desired duration here. Also, if you are fetching slots for a dynamic event then you can specify the duration her which defaults to 30, meaning that returned slots will be each 30 minutes long.",
"example": "60",
"schema": {
"type": "number"
}
},
{
"name": "format",
"required": false,
"in": "query",
"description": "Format of slot times in response. Use 'range' to get start and end times.",
"example": "range",
"schema": {
"enum": [
"range",
"time"
],
"type": "string"
}
},
{
"name": "routingFormId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResponseSlotsOutput"
}
}
}
}
},
"tags": [
"Routing forms"
]
}
},
"/v2/schedules": {
"post": {
"operationId": "SchedulesController_2024_06_11_createSchedule",
@@ -19962,6 +20057,179 @@
"data"
]
},
"SlotsOutput_2024_09_04": {
"type": "object",
"properties": {}
},
"RangeSlotsOutput_2024_09_04": {
"type": "object",
"properties": {}
},
"ResponseSlotsOutputData": {
"type": "object",
"properties": {
"eventTypeId": {
"type": "number"
},
"slots": {
"oneOf": [
{
"$ref": "#/components/schemas/SlotsOutput_2024_09_04"
},
{
"$ref": "#/components/schemas/RangeSlotsOutput_2024_09_04"
}
]
}
},
"required": [
"eventTypeId",
"slots"
]
},
"ResponseSlotsOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/ResponseSlotsOutputData"
}
},
"required": [
"status",
"data"
]
},
"ReserveSlotInput_2024_09_04": {
"type": "object",
"properties": {
"eventTypeId": {
"type": "number",
"example": 1,
"description": "The ID of the event type for which slot should be reserved."
},
"slotStart": {
"type": "string",
"example": "2024-09-04T09:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing available slot."
},
"slotDuration": {
"type": "number",
"example": "30",
"description": "By default slot duration is equal to event type length, but if you want to reserve a slot for an event type that has a variable length you can specify it here as a number in minutes. If you don't have this set explicitly that event type can have one of many lengths you can omit this."
},
"reservationDuration": {
"type": "number",
"example": 5,
"description": "ONLY for authenticated requests with api key, access token or OAuth credentials (ID + secret).\n \n For how many minutes the slot should be reserved - for this long time noone else can book this event type at `start` time. If not provided, defaults to 5 minutes."
}
},
"required": [
"eventTypeId",
"slotStart"
]
},
"ReserveSlotOutput_2024_09_04": {
"type": "object",
"properties": {
"eventTypeId": {
"type": "number",
"example": 1,
"description": "The ID of the event type for which slot was reserved."
},
"slotStart": {
"type": "string",
"example": "2024-09-04T09:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing available slot."
},
"slotEnd": {
"type": "string",
"example": "2024-09-04T10:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing slot end."
},
"slotDuration": {
"type": "number",
"example": "30",
"description": "By default slot duration is equal to event type length, but if you want to reserve a slot for an event type that has a variable length you can specify it here. If you don't have this set explicitly that event type can have one of many lengths you can omit this."
},
"reservationUid": {
"type": "string",
"example": "e84be5a3-4696-49e3-acc7-b2f3999c3b94",
"description": "The unique identifier of the reservation. Use it to update, get or delete the reservation."
},
"reservationDuration": {
"type": "number",
"example": 5,
"description": "For how many minutes the slot is reserved - for this long time noone else can book this event type at `start` time."
},
"reservationUntil": {
"type": "string",
"example": "2023-09-04T10:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing time until which the slot is reserved."
}
},
"required": [
"eventTypeId",
"slotStart",
"slotEnd",
"slotDuration",
"reservationUid",
"reservationDuration",
"reservationUntil"
]
},
"ReserveSlotOutputResponse_2024_09_04": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/ReserveSlotOutput_2024_09_04"
}
},
"required": [
"status",
"data"
]
},
"GetReservedSlotOutput_2024_09_04": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"nullable": true,
"allOf": [
{
"$ref": "#/components/schemas/GetReservedSlotOutput_2024_09_04"
}
]
}
},
"required": [
"status",
"data"
]
},
"MeOrgOutput": {
"type": "object",
"properties": {
@@ -22255,129 +22523,6 @@
"data"
]
},
"ReserveSlotInput_2024_09_04": {
"type": "object",
"properties": {
"eventTypeId": {
"type": "number",
"example": 1,
"description": "The ID of the event type for which slot should be reserved."
},
"slotStart": {
"type": "string",
"example": "2024-09-04T09:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing available slot."
},
"slotDuration": {
"type": "number",
"example": "30",
"description": "By default slot duration is equal to event type length, but if you want to reserve a slot for an event type that has a variable length you can specify it here as a number in minutes. If you don't have this set explicitly that event type can have one of many lengths you can omit this."
},
"reservationDuration": {
"type": "number",
"example": 5,
"description": "ONLY for authenticated requests with api key, access token or OAuth credentials (ID + secret).\n \n For how many minutes the slot should be reserved - for this long time noone else can book this event type at `start` time. If not provided, defaults to 5 minutes."
}
},
"required": [
"eventTypeId",
"slotStart"
]
},
"ReserveSlotOutput_2024_09_04": {
"type": "object",
"properties": {
"eventTypeId": {
"type": "number",
"example": 1,
"description": "The ID of the event type for which slot was reserved."
},
"slotStart": {
"type": "string",
"example": "2024-09-04T09:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing available slot."
},
"slotEnd": {
"type": "string",
"example": "2024-09-04T10:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing slot end."
},
"slotDuration": {
"type": "number",
"example": "30",
"description": "By default slot duration is equal to event type length, but if you want to reserve a slot for an event type that has a variable length you can specify it here. If you don't have this set explicitly that event type can have one of many lengths you can omit this."
},
"reservationUid": {
"type": "string",
"example": "e84be5a3-4696-49e3-acc7-b2f3999c3b94",
"description": "The unique identifier of the reservation. Use it to update, get or delete the reservation."
},
"reservationDuration": {
"type": "number",
"example": 5,
"description": "For how many minutes the slot is reserved - for this long time noone else can book this event type at `start` time."
},
"reservationUntil": {
"type": "string",
"example": "2023-09-04T10:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing time until which the slot is reserved."
}
},
"required": [
"eventTypeId",
"slotStart",
"slotEnd",
"slotDuration",
"reservationUid",
"reservationDuration",
"reservationUntil"
]
},
"ReserveSlotOutputResponse_2024_09_04": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/ReserveSlotOutput_2024_09_04"
}
},
"required": [
"status",
"data"
]
},
"GetReservedSlotOutput_2024_09_04": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"nullable": true,
"allOf": [
{
"$ref": "#/components/schemas/GetReservedSlotOutput_2024_09_04"
}
]
}
},
"required": [
"status",
"data"
]
},
"UserWebhookOutputDto": {
"type": "object",
"properties": {
+502 -371
View File
@@ -1935,268 +1935,6 @@
"tags": ["Orgs / Memberships"]
}
},
"/v2/organizations/{orgId}/organizations": {
"post": {
"operationId": "OrganizationsOrganizationsController_createOrganization",
"summary": "Create an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateOrganizationInput"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateManagedOrganizationOutput"
}
}
}
}
},
"tags": ["Orgs / Orgs"]
},
"get": {
"operationId": "OrganizationsOrganizationsController_getOrganizations",
"summary": "Get all organizations within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationsOutput"
}
}
}
}
},
"tags": ["Orgs / Orgs"]
}
},
"/v2/organizations/{orgId}/organizations/{managedOrganizationId}": {
"get": {
"operationId": "OrganizationsOrganizationsController_getOrganization",
"summary": "Get an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": ["Orgs / Orgs"]
},
"patch": {
"operationId": "OrganizationsOrganizationsController_updateOrganization",
"summary": "Update an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganizationInput"
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": ["Orgs / Orgs"]
},
"delete": {
"operationId": "OrganizationsOrganizationsController_deleteOrganization",
"summary": "Delete an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": ["Orgs / Orgs"]
}
},
"/v2/organizations/{orgId}/routing-forms": {
"get": {
"operationId": "OrganizationsRoutingFormsController_getOrganizationRoutingForms",
@@ -6776,7 +6514,7 @@
"/v2/bookings/{bookingUid}/reassign": {
"post": {
"operationId": "BookingsController_2024_08_13_reassignBooking",
"summary": "Reassign booking to auto-selected host",
"summary": "Reassign a booking to auto-selected host",
"description": "The provided authorization header refers to the owner of the booking.",
"parameters": [
{
@@ -8264,6 +8002,268 @@
"tags": ["Event Types / Webhooks"]
}
},
"/v2/organizations/{orgId}/organizations": {
"post": {
"operationId": "OrganizationsOrganizationsController_createOrganization",
"summary": "Create an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateOrganizationInput"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateManagedOrganizationOutput"
}
}
}
}
},
"tags": ["Managed Orgs"]
},
"get": {
"operationId": "OrganizationsOrganizationsController_getOrganizations",
"summary": "Get all organizations within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationsOutput"
}
}
}
}
},
"tags": ["Managed Orgs"]
}
},
"/v2/organizations/{orgId}/organizations/{managedOrganizationId}": {
"get": {
"operationId": "OrganizationsOrganizationsController_getOrganization",
"summary": "Get an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": ["Managed Orgs"]
},
"patch": {
"operationId": "OrganizationsOrganizationsController_updateOrganization",
"summary": "Update an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "orgId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateOrganizationInput"
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": ["Managed Orgs"]
},
"delete": {
"operationId": "OrganizationsOrganizationsController_deleteOrganization",
"summary": "Delete an organization within an organization",
"description": "Requires the user to have at least the 'ORG_ADMIN' role within the organization. Additionally, for platform, the plan must be 'SCALE' or higher to access this endpoint.",
"parameters": [
{
"name": "x-cal-secret-key",
"in": "header",
"description": "For platform customers - OAuth client secret key",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "x-cal-client-id",
"in": "header",
"description": "For platform customers - OAuth client ID",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "managedOrganizationId",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetManagedOrganizationOutput"
}
}
}
}
},
"tags": ["Managed Orgs"]
}
},
"/v2/me": {
"get": {
"operationId": "MeController_getMe",
@@ -8520,6 +8520,96 @@
"tags": ["OAuth Clients"]
}
},
"/v2/routing-forms/{routingFormId}/calculate-slots": {
"post": {
"operationId": "RoutingFormsController_calculateSlotsBasedOnRoutingFormResponse",
"summary": "Calculate slots based on routing form response",
"description": "It will not actually save the response just return the routed event type and slots when it can be booked.",
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "start",
"required": true,
"in": "query",
"description": "\n Time starting from which available slots should be checked.\n \n Must be in UTC timezone as ISO 8601 datestring.\n \n You can pass date without hours which defaults to start of day or specify hours:\n 2024-08-13 (will have hours 00:00:00 aka at very beginning of the date) or you can specify hours manually like 2024-08-13T09:00:00Z\n ",
"example": "2050-09-05",
"schema": {
"type": "string"
}
},
{
"name": "end",
"required": true,
"in": "query",
"description": "\n Time until which available slots should be checked.\n \n Must be in UTC timezone as ISO 8601 datestring.\n \n You can pass date without hours which defaults to end of day or specify hours:\n 2024-08-20 (will have hours 23:59:59 aka at the very end of the date) or you can specify hours manually like 2024-08-20T18:00:00Z",
"example": "2050-09-06",
"schema": {
"type": "string"
}
},
{
"name": "timeZone",
"required": false,
"in": "query",
"description": "Time zone in which the available slots should be returned. Defaults to UTC.",
"example": "Europe/Rome",
"schema": {
"type": "string"
}
},
{
"name": "duration",
"required": false,
"in": "query",
"description": "If event type has multiple possible durations then you can specify the desired duration here. Also, if you are fetching slots for a dynamic event then you can specify the duration her which defaults to 30, meaning that returned slots will be each 30 minutes long.",
"example": "60",
"schema": {
"type": "number"
}
},
{
"name": "format",
"required": false,
"in": "query",
"description": "Format of slot times in response. Use 'range' to get start and end times.",
"example": "range",
"schema": {
"enum": ["range", "time"],
"type": "string"
}
},
{
"name": "routingFormId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResponseSlotsOutput"
}
}
}
}
},
"tags": ["Routing forms"]
}
},
"/v2/schedules": {
"post": {
"operationId": "SchedulesController_2024_06_11_createSchedule",
@@ -18223,6 +18313,155 @@
},
"required": ["status", "data"]
},
"SlotsOutput_2024_09_04": {
"type": "object",
"properties": {}
},
"RangeSlotsOutput_2024_09_04": {
"type": "object",
"properties": {}
},
"ResponseSlotsOutputData": {
"type": "object",
"properties": {
"eventTypeId": {
"type": "number"
},
"slots": {
"oneOf": [
{
"$ref": "#/components/schemas/SlotsOutput_2024_09_04"
},
{
"$ref": "#/components/schemas/RangeSlotsOutput_2024_09_04"
}
]
}
},
"required": ["eventTypeId", "slots"]
},
"ResponseSlotsOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": ["success", "error"]
},
"data": {
"$ref": "#/components/schemas/ResponseSlotsOutputData"
}
},
"required": ["status", "data"]
},
"ReserveSlotInput_2024_09_04": {
"type": "object",
"properties": {
"eventTypeId": {
"type": "number",
"example": 1,
"description": "The ID of the event type for which slot should be reserved."
},
"slotStart": {
"type": "string",
"example": "2024-09-04T09:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing available slot."
},
"slotDuration": {
"type": "number",
"example": "30",
"description": "By default slot duration is equal to event type length, but if you want to reserve a slot for an event type that has a variable length you can specify it here as a number in minutes. If you don't have this set explicitly that event type can have one of many lengths you can omit this."
},
"reservationDuration": {
"type": "number",
"example": 5,
"description": "ONLY for authenticated requests with api key, access token or OAuth credentials (ID + secret).\n \n For how many minutes the slot should be reserved - for this long time noone else can book this event type at `start` time. If not provided, defaults to 5 minutes."
}
},
"required": ["eventTypeId", "slotStart"]
},
"ReserveSlotOutput_2024_09_04": {
"type": "object",
"properties": {
"eventTypeId": {
"type": "number",
"example": 1,
"description": "The ID of the event type for which slot was reserved."
},
"slotStart": {
"type": "string",
"example": "2024-09-04T09:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing available slot."
},
"slotEnd": {
"type": "string",
"example": "2024-09-04T10:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing slot end."
},
"slotDuration": {
"type": "number",
"example": "30",
"description": "By default slot duration is equal to event type length, but if you want to reserve a slot for an event type that has a variable length you can specify it here. If you don't have this set explicitly that event type can have one of many lengths you can omit this."
},
"reservationUid": {
"type": "string",
"example": "e84be5a3-4696-49e3-acc7-b2f3999c3b94",
"description": "The unique identifier of the reservation. Use it to update, get or delete the reservation."
},
"reservationDuration": {
"type": "number",
"example": 5,
"description": "For how many minutes the slot is reserved - for this long time noone else can book this event type at `start` time."
},
"reservationUntil": {
"type": "string",
"example": "2023-09-04T10:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing time until which the slot is reserved."
}
},
"required": [
"eventTypeId",
"slotStart",
"slotEnd",
"slotDuration",
"reservationUid",
"reservationDuration",
"reservationUntil"
]
},
"ReserveSlotOutputResponse_2024_09_04": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": ["success", "error"]
},
"data": {
"$ref": "#/components/schemas/ReserveSlotOutput_2024_09_04"
}
},
"required": ["status", "data"]
},
"GetReservedSlotOutput_2024_09_04": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": ["success", "error"]
},
"data": {
"nullable": true,
"allOf": [
{
"$ref": "#/components/schemas/GetReservedSlotOutput_2024_09_04"
}
]
}
},
"required": ["status", "data"]
},
"MeOrgOutput": {
"type": "object",
"properties": {
@@ -20308,114 +20547,6 @@
},
"required": ["status", "data"]
},
"ReserveSlotInput_2024_09_04": {
"type": "object",
"properties": {
"eventTypeId": {
"type": "number",
"example": 1,
"description": "The ID of the event type for which slot should be reserved."
},
"slotStart": {
"type": "string",
"example": "2024-09-04T09:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing available slot."
},
"slotDuration": {
"type": "number",
"example": "30",
"description": "By default slot duration is equal to event type length, but if you want to reserve a slot for an event type that has a variable length you can specify it here as a number in minutes. If you don't have this set explicitly that event type can have one of many lengths you can omit this."
},
"reservationDuration": {
"type": "number",
"example": 5,
"description": "ONLY for authenticated requests with api key, access token or OAuth credentials (ID + secret).\n \n For how many minutes the slot should be reserved - for this long time noone else can book this event type at `start` time. If not provided, defaults to 5 minutes."
}
},
"required": ["eventTypeId", "slotStart"]
},
"ReserveSlotOutput_2024_09_04": {
"type": "object",
"properties": {
"eventTypeId": {
"type": "number",
"example": 1,
"description": "The ID of the event type for which slot was reserved."
},
"slotStart": {
"type": "string",
"example": "2024-09-04T09:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing available slot."
},
"slotEnd": {
"type": "string",
"example": "2024-09-04T10:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing slot end."
},
"slotDuration": {
"type": "number",
"example": "30",
"description": "By default slot duration is equal to event type length, but if you want to reserve a slot for an event type that has a variable length you can specify it here. If you don't have this set explicitly that event type can have one of many lengths you can omit this."
},
"reservationUid": {
"type": "string",
"example": "e84be5a3-4696-49e3-acc7-b2f3999c3b94",
"description": "The unique identifier of the reservation. Use it to update, get or delete the reservation."
},
"reservationDuration": {
"type": "number",
"example": 5,
"description": "For how many minutes the slot is reserved - for this long time noone else can book this event type at `start` time."
},
"reservationUntil": {
"type": "string",
"example": "2023-09-04T10:00:00Z",
"description": "ISO 8601 datestring in UTC timezone representing time until which the slot is reserved."
}
},
"required": [
"eventTypeId",
"slotStart",
"slotEnd",
"slotDuration",
"reservationUid",
"reservationDuration",
"reservationUntil"
]
},
"ReserveSlotOutputResponse_2024_09_04": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": ["success", "error"]
},
"data": {
"$ref": "#/components/schemas/ReserveSlotOutput_2024_09_04"
}
},
"required": ["status", "data"]
},
"GetReservedSlotOutput_2024_09_04": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": ["success", "error"]
},
"data": {
"nullable": true,
"allOf": [
{
"$ref": "#/components/schemas/GetReservedSlotOutput_2024_09_04"
}
]
}
},
"required": ["status", "data"]
},
"UserWebhookOutputDto": {
"type": "object",
"properties": {
+2 -2
View File
@@ -1,6 +1,7 @@
// !IMPORTANT! changes to this file requires publishing new version of platform libraries in order for the changes to be applied to APIV2
import type { GetServerSidePropsContext } from "next";
import { stringify } from "querystring";
import { v4 as uuidv4 } from "uuid";
import z from "zod";
import { enrichFormWithMigrationData } from "@calcom/app-store/routing-forms/enrichFormWithMigrationData";
@@ -19,6 +20,7 @@ import { isAuthorizedToViewFormOnOrgDomain } from "@calcom/features/routing-form
import logger from "@calcom/lib/logger";
import monitorCallbackAsync from "@calcom/lib/sentryWrapper";
import { RoutingFormRepository } from "@calcom/lib/server/repository/routingForm";
import { UserRepository } from "@calcom/lib/server/repository/user";
import { TRPCError } from "@trpc/server";
@@ -76,7 +78,6 @@ const _getRoutedUrl = async (context: Pick<GetServerSidePropsContext, "query" |
};
}
const { UserRepository } = await import("@calcom/lib/server/repository/user");
const profileEnrichmentStart = performance.now();
const formWithUserProfile = {
...form,
@@ -119,7 +120,6 @@ const _getRoutedUrl = async (context: Pick<GetServerSidePropsContext, "query" |
const decidedAction = matchingRoute.action;
const { v4: uuidv4 } = await import("uuid");
let teamMembersMatchingAttributeLogic = null;
let formResponseId = null;
let attributeRoutingConfig = null;
@@ -13,7 +13,7 @@ import {
import { SlotFormat } from "@calcom/platform-enums";
class GetAvailableSlotsInput_2024_09_04 {
export class GetAvailableSlotsInput_2024_09_04 {
@IsDateString()
@ApiProperty({
type: String,
+4947 -93
View File
File diff suppressed because it is too large Load Diff