API v2 Service, Auth, and API Keys
Purpose and Scope
Cal.diy API v2 is the Nest.js service used for the platform API surface. This page explains how the service starts in local and serverless environments, what setup is expected before running it, and how the small API-key utility surface fits into authentication code. It is intended for developers operating or modifying the API v2 app rather than end users configuring calendar integrations. The service is part of the wider Cal.diy self-hosted monorepo, so its local workflow assumes the same dependency installation, environment-file discipline, database access, and package rebuild behavior used elsewhere in the repository.
The most important distinction is that API v2 has two startup modes. In normal local development, the entrypoint creates a Nest Express application, bootstraps platform-wide application behavior, reads the configured port, optionally generates Swagger in development, and starts listening. In serverless hosting, the default export obtains a cached Express instance and delegates each request to that instance. That design keeps local development straightforward while avoiding repeated Nest application initialization during a serverless container lifecycle. Sources: apps/api/v2/README.md, apps/api/v2/src/main.ts
Relevant Source Files
apps/api/v2/README.md- Local development instructions, runtime commands, database and license-key setup notes, dependency rebuild guidance, test commands, and guard conventions for API v2.apps/api/v2/src/main.ts- Service entrypoint that creates the Nest application, runs local startup, exposes the serverless handler, caches the Express instance, and parses query strings for serverless requests.apps/api/v2/src/lib/api-key/index.ts- Public API-key helper module exporting token hashing, prefix detection, and prefix-stripping utilities.apps/api/v2/src/modules/auth/guards/or-guard/index.ts- Barrel export for theOrguard helper namespace used by authentication guard composition.
Local Development Setup
The README defines API v2 as a Nest.js project and starts with the development path. After installing dependencies with yarn install, Docker should be installed and running. The service also expects MailHog for local email delivery; the documented way to start it is from packages/emails with yarn dx. API v2 has its own environment file: copy apps/api/v2/.env.example to apps/api/v2/.env. The README specifically calls out NEXTAUTH_SECRET as a shared secret that must match between the root .env and the API v2 .env, because authentication state has to be understood consistently by the web app and the API service. Sources: apps/api/v2/README.md
A local API v2 environment also needs a license-key row in the database and a matching CALCOM_LICENSE_KEY environment variable. The README shows an entry for the Deployment table with id 1 and a zero UUID license key, then sets CALCOM_LICENSE_KEY="00000000-0000-0000-0000-000000000000" in apps/api/v2/.env. Optional Prisma setup is run from packages/prisma using yarn prisma generate, yarn prisma migrate dev, and yarn db-seed. These steps make the API service less isolated than a standalone Nest example: it depends on shared database schema, shared auth configuration, and shared development services.
yarn install
cd packages/emails && yarn dx
# copy apps/api/v2/.env.example to apps/api/v2/.env
# ensure NEXTAUTH_SECRET matches the root .env
cd packages/prisma
yarn prisma generate
yarn prisma migrate dev
yarn db-seedStartup and Execution Flow
For local development, the documented command is yarn dev. The entrypoint reinforces that behavior by checking process.env.VERCEL; when that value is not present, it calls run() and exits the process on startup failure. The run() function creates the Nest application, calls bootstrap(app), reads api.port from ConfigService<AppConfig, true>, generates Swagger only when the configured environment type is development, and listens on the configured port. Startup failures are logged as local startup crashes rather than silently ignored, which is useful when diagnosing missing environment values or unavailable services. Sources: apps/api/v2/src/main.ts
Serverless execution uses a different path. The default export receives an Express Request and Response, obtains the cached server from NestServer.getInstance(), reparses the request query string with qs.parse when a query string exists, and then invokes the Express instance as the request handler. The source comments explain the reason for reparsing: serverless platforms such as Vercel or AWS can simplify array query parameters, while the API wants support for formats such as ?ids[]=1&ids[]=2. If initialization fails in this path, the handler logs a critical error and returns 500 Internal Server Error: Initialization Failed. Sources: apps/api/v2/src/main.ts
The NestServer singleton is the bridge between Nest and serverless containers. Its getInstance() method creates the app only when the cached Express server is absent. It then calls bootstrap(app), awaits app.init() so modules and database connections are initialized, extracts the Express instance from the Nest HTTP adapter, and stores it for reuse. In production, the entrypoint also assigns process.env.TRIGGER_VERSION from the imported trigger version. These details matter for deployment troubleshooting: a service that starts locally may still fail in serverless mode if initialization work, module resolution, or environment variables differ.
# normal local API v2 development
yarn dev
# build and run without watch mode if unrelated file changes restart the service
cd apps/api/v2
yarn dev:build
yarn start
# rebuild watched platform dependencies in a second terminal
yarn run dev:build:watch
# alternative without Docker
yarn dev:no-dockerAuthentication Guard Structure
The README records the conventions that API v2 guard implementations are expected to follow. A guard that cannot activate a request should throw ForbiddenException with an error message that includes the guard name and the error, rather than returning false. It should also avoid caching negative authorization results in Redis. Only successful guard results, where access is allowed, should be cached. This is a practical self-hosting concern: if an operator fixes an account, permission, or configuration problem, a cached denial would otherwise keep rejecting requests until the cache expires. Sources: apps/api/v2/README.md
The README also documents how ApiAuthGuard is narrowed. If a route uses ApiAuthGuard but should allow only a specific authentication method, such as API keys, the route must also use @ApiAuthGuardOnlyAllow(["API_KEY"]) under @UseGuards(ApiAuthGuard). If the narrowing decorator is absent, empty, or receives no methods, all authentication methods are allowed by ApiAuthGuard. The or-guard module exposes Or from ./or.guard, indicating that guard composition is packaged as a public auth guard helper even though callers should still follow the README’s failure and caching rules. Sources: apps/api/v2/README.md, apps/api/v2/src/modules/auth/guards/or-guard/index.ts
API-Key Helper Reference
The API-key library is intentionally compact and suitable for reuse by guards, services, or tests that need consistent token handling. sha256Hash(token: string): string hashes a token with Node’s crypto.createHash("sha256"), updates the hash with the token, and returns the hexadecimal digest. isApiKey(authString: string, prefix: string): boolean checks whether an authorization string starts with the provided prefix, defaulting to cal_ when the prefix is nullish. stripApiKey(apiKey: string, prefix?: string): string removes the configured prefix, also defaulting to cal_. Sources: apps/api/v2/src/lib/api-key/index.ts
| Export | Signature | Behavior |
|---|---|---|
sha256Hash | (token: string) => string | Returns a SHA-256 hex digest for token storage or comparison workflows. |
isApiKey | (authString: string, prefix: string) => boolean | Detects whether an auth string starts with the configured prefix, defaulting to cal_. |
stripApiKey | (apiKey: string, prefix?: string) => string | Removes the configured prefix from a presented API key, defaulting to cal_. |
These helpers establish a small convention rather than a full authentication framework. Code that accepts presented keys can detect the prefix, remove it before downstream processing, and hash the remaining value for comparison without duplicating crypto or prefix logic. Because stripApiKey uses string replacement with the configured prefix, callers should pass values that have already been recognized as API keys when strict handling is required. The README’s ApiAuthGuardOnlyAllow(["API_KEY"]) convention is the route-level piece that tells API v2 to accept only this kind of authentication method.
Testing and Troubleshooting Signals
API v2 includes the usual Nest project test commands in its README: yarn run test for unit tests, yarn run test:e2e for end-to-end tests, yarn run test:e2e some-file.e2e-spec.ts for a specific end-to-end file in watch mode, and yarn run test:cov for coverage. The same README warns that API v2 depends on platform packages named platform-libraries, platform-constants, platform-enums, platform-utils, and platform-types. If one of those dependencies changes, restart API v2 or run the watch build command so the service rebuilds and picks up the change. Sources: apps/api/v2/README.md
When API v2 does not start, check the startup path before changing route code. Confirm Docker and MailHog are running if you are using the documented local setup, confirm apps/api/v2/.env exists, confirm NEXTAUTH_SECRET matches the root environment, and confirm the license-key database row and CALCOM_LICENSE_KEY value are aligned. For deployed instances, also review URL and authentication environment variables used by the web app, because official troubleshooting notes for Cal.diy frequently tie authentication failures to incorrect host, redirect, or session configuration. After the service boots, use the guard conventions and API-key helper reference above when adding or debugging protected endpoints.
Next Steps
Read api-v2-overview for the broader API v2 service model, troubleshooting-api-database for deployment and connectivity failures, and platform-atoms-overview if you are using API v2 together with platform atoms. When adding a protected route, start with the route’s allowed authentication methods, choose whether ApiAuthGuardOnlyAllow is needed, reuse the API-key helpers instead of duplicating token parsing, and run the relevant unit or end-to-end tests before deploying.