* fix: resolve flaky org-admin integration tests - Fixed isAdminGuard Prisma query to use explicit 'is' filter for organizationSettings - Fixed async describe with top-level awaits in _get.integration-test.ts - Added global setup in setupVitest.ts to prevent race conditions - Removed duplicate setup logic from individual test files Root cause: Tests were running in parallel with independent beforeAll setups, causing race conditions where organizationSettings weren't created before tests executed. The async describe with top-level awaits made this worse by executing queries before beforeAll hooks ran. Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: move org-admin setup to integration-only setup file The global setup in setupVitest.ts was running for ALL test workspaces (including unit tests), causing ECONNREFUSED errors because unit tests don't have database access. Changes: - Created setupVitest.integration.ts with org-admin seeding logic - Removed database seeding from setupVitest.ts - Updated vitest.workspace.ts to use integration-only setup file - Added DATABASE_URL guard to prevent errors when DB is unavailable This fixes the unit test failures while preserving the fix for flaky integration tests. Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: use globalSetup instead of setupFiles for org-admin seeding The previous fix using setupFiles didn't work because setupFiles run AFTER test modules are evaluated. This meant any top-level Prisma queries in test files would execute before the org-admin seeding. Changes: - Moved org-admin seeding to tests/integration/global-setup.ts - Updated vitest.workspace.ts to use globalSetup for IntegrationTests - globalSetup runs BEFORE any test modules are loaded, ensuring org settings exist before tests execute - Added teardown function to properly disconnect Prisma after tests This ensures org-admin state is seeded once before all integration tests run, eliminating the race condition and ensuring tests have the correct database state. Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * debug: add logging to globalSetup to diagnose why tests are failing Added console.log statements throughout the globalSetup to verify: - Whether the globalSetup is running at all - Whether DATABASE_URL is available - Whether the org teams are found in the database - Whether the upserts are executing successfully This will help diagnose why the integration tests are still failing with org-admin not being detected. Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: use absolute path for globalSetup in vitest.workspace.ts Changed from relative path 'tests/integration/global-setup.ts' to absolute path using new URL().pathname to ensure Vitest can properly locate and load the globalSetup file. This should fix the issue where the globalSetup wasn't being executed at all (no logs appearing in CI). Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: add serial execution to IntegrationTests workspace Added sequence.concurrent: false to IntegrationTests workspace to eliminate inter-file race conditions while stabilizing org-admin seeding. This ensures tests run one at a time, preventing parallel execution issues that could cause flaky test failures. This is a temporary stabilizer that can be reverted once the globalSetup seeding is confirmed working. Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * refactor: use TeamRepository in globalSetup to follow architectural rule Refactored globalSetup to use TeamRepository instead of direct Prisma access, following the 'No prisma outside of repositories' architectural rule. Changes: - Created TeamRepository class with methods for finding organizations and upserting organization settings - Updated globalSetup to use TeamRepository.withGlobalPrisma() - Removed direct Prisma imports from globalSetup This ensures proper separation of concerns and follows the repository pattern established in the codebase. Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: use relative path import for TeamRepository in globalSetup Changed from package-scoped import '@calcom/lib/server/repository/team' to relative path import '../../packages/lib/server/repository/team' to fix module resolution issue. Added try/catch with logging around the import to surface any remaining resolution issues in CI logs. This should allow the globalSetup to execute properly and seed org-admin state before tests run. Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * debug: add membership logging to globalSetup to diagnose test failures Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: ensure owner1-acme membership exists in globalSetup Root cause: CI database snapshot doesn't include the owner1-acme OWNER membership that exists in the current seed file, because cache-db action's cache key doesn't include scripts/seed.ts. Solution: Add ensureMembership method to TeamRepository and call it in globalSetup to ensure the owner1-acme user has an accepted OWNER membership in the Acme org before tests run. This fixes the 5 failing org-admin integration tests that depend on this membership. Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: ensure all 10 member{0-9}-acme users exist in globalSetup Add ensureUser method to TeamRepository to create users if they don't exist. Update ensureMembership to accept MEMBER role in addition to OWNER and ADMIN. Ensure all 10 member{0-9}-acme users are created with MEMBER role and accepted: true in the Acme org. This should fix the remaining 4 failing tests that expect multiple org members to exist. Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: use upsert instead of create in ensureUser to avoid unique constraint violations The ensureUser method was using create which could fail if a user with that email already exists. Switch to upsert to make the operation idempotent and avoid P2002 unique constraint errors. Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * refactor: clean up debug code and move test repository to proper location - Remove all console.log debug statements from global-setup.ts - Remove serial execution from IntegrationTests workspace (restore parallel execution) - Move TeamRepository to tests/lib/test-team-repository.ts and rename to TestTeamRepository - Keep all actual fixes: isAdmin Prisma query fix, ensureUser/ensureMembership methods, globalSetup seeding Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Commercial Cal.com Public API
Welcome to the Public API ("/apps/api") of the Cal.com.
This is the public REST api for cal.com. It exposes CRUD Endpoints of all our most important resources. And it makes it easy for anyone to integrate with Cal.com at the application programming level.
Stack
- NextJS
- TypeScript
- Prisma
Development
Setup
-
Clone the main repo (NOT THIS ONE)
git clone --recurse-submodules -j8 https://github.com/calcom/cal.com.git -
Go to the project folder
cd cal.com -
Copy
apps/api/.env.exampletoapps/api/.envcp apps/api/.env.example apps/api/.env cp .env.example .env -
Install packages with yarn
yarn -
Start developing
yarn workspace @calcom/api dev -
Open http://localhost:3002 with your browser to see the result.
API Authentication (API Keys)
The API requires a valid apiKey query param to be passed: You can generate them at https://app.cal.com/settings/security
For example:
GET https://api.cal.com/v1/users?apiKey={INSERT_YOUR_CAL.COM_API_KEY_HERE}
API Keys optionally may have expiry dates, if they are expired they won't work. If you create an apiKey without a userId relation, it won't work either for now as it relies on it to establish the current authenticated user.
In the future we might add support for header Bearer Auth if we need to or if our customers require it.
Middlewares
We don't use the new NextJS 12 Beta Middlewares, mainly because they run on the edge, and are not able to call prisma from api endpoints. We use instead a very nifty library called next-api-middleware that let's us use a similar approach building our own middlewares and applying them as we see fit.
- withMiddleware() requires some default middlewares (verifyApiKey, etc...)
Next.config.js
Redirects
Since this is an API only project, we don't want to have to type /api/ in all the routes, and so redirect all traffic to api, so a call to api.cal.com/v1 will resolve to api.cal.com/api/v1
Likewise, v1 is added as param query called version to final /api call so we don't duplicate endpoints in the future for versioning if needed.
Transpiling locally shared monorepo modules
We're calling several packages from monorepo, this need to be transpiled before building since are not available as regular npm packages. That's what withTM does.
"@calcom/app-store",
"@calcom/prisma",
"@calcom/lib",
"@calcom/features",
API Endpoint Validation
We validate that only the supported methods are accepted at each endpoint, so in
- /endpoint: you can only [GET] (all) and [POST] (create new)
- /endpoint/id: you can read create and edit [GET, PATCH, DELETE]
Zod Validations
The API uses zod library like our main web repo. It validates that either GET query parameters or POST body content's are valid and up to our spec. It gives errors when parsing result's with schemas and failing validation.
We use it in several ways, but mainly, we first import the auto-generated schema from @calcom/prisma for each model, which lives in lib/validations/
We have some shared validations which several resources require, like baseApiParams which parses apiKey in all requests, or querIdAsString or TransformParseInt which deal with the id's coming from req.query.
-
[*]BaseBodyParams that omits any values from the model that are too sensitive or we don't want to pick when creating a new resource like id, userId, etc.. (those are gotten from context or elsewhere)
-
[*]Public that also omits any values that we don't want to expose when returning the model as a response, which we parse against before returning all resources.
-
[*]BodyParams which merges both
[*]BaseBodyParams.merge([*]RequiredParams);
Next Validations
Next-Validations Docs Next-Validations Repo We also use this useful helper library that let us wrap our endpoints in a validate HOC that checks the req against our validation schema built out with zod for either query and / or body's requests.
Testing with Jest + node-mocks-http
We aim to provide a fully tested API for our peace of mind, this is accomplished by using jest + node-mocks-http
Endpoints matrix
| resource | get [id] | get all | create | edit | delete |
|---|---|---|---|---|---|
| attendees | ✅ | ✅ | ✅ | ✅ | ✅ |
| availabilities | ✅ | ✅ | ✅ | ✅ | ✅ |
| booking-references | ✅ | ✅ | ✅ | ✅ | ✅ |
| event-references | ✅ | ✅ | ✅ | ✅ | ✅ |
| destination-calendars | ✅ | ✅ | ✅ | ✅ | ✅ |
| custom-inputs | ✅ | ✅ | ✅ | ✅ | ✅ |
| event-types | ✅ | ✅ | ✅ | ✅ | ✅ |
| memberships | ✅ | ✅ | ✅ | ✅ | ✅ |
| payments | ✅ | ✅ | ❌ | ❌ | ❌ |
| schedules | ✅ | ✅ | ✅ | ✅ | ✅ |
| selected-calendars | ✅ | ✅ | ✅ | ✅ | ✅ |
| teams | ✅ | ✅ | ✅ | ✅ | ✅ |
| users | ✅ | 👤[1] | ✅ | ✅ | ✅ |
Models from database that are not exposed
mostly because they're deemed too sensitive can be revisited if needed. Also they are expected to be used via cal's webapp.
- Api Keys
- Credentials
- Webhooks
- ResetPasswordRequest
- VerificationToken
- ReminderMail
Documentation (OpenAPI)
You will see that each endpoint has a comment at the top with the annotation @swagger with the documentation of the endpoint, please update it if you change the code! This is what auto-generates the OpenAPI spec by collecting the YAML in each endpoint and parsing it in /docs alongside the json-schema (auto-generated from prisma package, not added to code but manually for now, need to fix later)
@calcom/apps/swagger
The documentation of the API lives inside the code, and it's auto-generated, the only endpoints that return without a valid apiKey are the homepage, with a JSON message redirecting you to the docs. and the /docs endpoint, which returns the OpenAPI 3.0 JSON Spec. Which SwaggerUi then consumes and generates the docs on.
Deployment
scripts/vercel-deploy.sh
The API is deployed to vercel.com, it uses a similar deployment script to website or webapp, and requires transpilation of several shared packages that are part of our turborepo ["app-store", "prisma", "lib", "ee"]
in order to build and deploy properly.
Envirorment variables
Required
DATABASE_URL=DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
Optional
APIKEY_PREFIX=cal# This can be changed per envirorment so caltest for staging for example.
If you're self-hosting under our commercial license, you can use any prefix you want for api keys. either leave the default cal_ (not providing any envirorment variable) or modify it
Ensure that while testing swagger, API project should be run in production mode We make sure of this by not using next in dev, but next build && next start, if you want hot module reloading and such when developing, please use yarn run next directly on apps/api.
See https://github.com/vercel/next.js/blob/canary/packages/next/server/dev/hot-reloader.ts#L79. Here in dev mode OPTIONS method is hardcoded to return only GET and OPTIONS as allowed method. Running in Production mode would cause this file to be not used. This is hot-reloading logic only. To remove this limitation, we need to ensure that on local endpoints are requested by swagger at /api/v1 and not /v1
How to deploy
We recommend deploying API in vercel.
There's some settings that you'll need to setup.
Under Vercel > Your API Project > Settings
In General > Build & Development Settings
BUILD COMMAND: yarn turbo run build --filter=@calcom/api --no-deps
OUTPUT DIRECTORY: apps/api/.next
In Git > Ignored Build Step
Add this command: ./scripts/vercel-deploy.sh
See scripts/vercel-deploy.sh for more info on how the deployment is done.
❗ IMPORTANT: If you're forking the API repo you will need to update the URLs in both the main repo
.gitmodulesand this repo./scripts/vercel-deploy.sh❗
Environment variables
Lastly API requires an env var for DATABASE_URL and CALCOM_LICENSE_KEY