Skip to content

Bug: Booking Limits Not Enforced for Recurring Bookings Created via API v2 #28542

Description

@Amitverma0509

Issue Summary

When a recurring booking is created through the API v2 endpoint (POST /v2/bookings) using the recurrenceCount field, any booking limits configured on the event type — such as "Max 1 booking per day" or "Max 3 bookings per week" — are only evaluated against the first occurrence. All subsequent occurrences in the same recurring request are inserted without re-checking the limit. This allows an attendee to silently bypass daily, weekly, monthly, or yearly booking caps in a single API call.

This is a correctness and fairness bug affecting any host who has booking limits configured and whose event types are booked through the v2 API — including platform customers, Zapier / Make / n8n automation users, and custom booking UIs built on the Cal.com Platform product.


Steps to Reproduce

  1. Log in to cal.com (or a self-hosted instance on main).
  2. Go to Event Types → create or edit an event type with the following settings:
    • Duration: 30 minutes
    • Booking limits: Max 1 booking per day
    • Recurring bookings: enabled
  3. Obtain a valid API v2 access token (via OAuth or personal access token) for the event owner.
  4. Send the following HTTP request:
POST https://api.cal.com/v2/bookings
Authorization: Bearer <YOUR_TOKEN>
Content-Type: application/json

{
  "eventTypeId": <YOUR_EVENT_TYPE_ID>,
  "start": "2026-04-01T10:00:00.000Z",
  "attendee": {
    "name": "Test Attendee",
    "email": "attendee@example.com",
    "timeZone": "UTC"
  },
  "recurrenceCount": 5
}
  1. Check the HTTP response — all 5 bookings return 201 Created.
  2. Call GET /v2/bookings and observe that 5 booking records now exist for the same host, all on the same day.

Actual Results

  • All 5 recurring occurrences are created unconditionally, ignoring the "Max 1 booking per day" limit.
  • The checkBookingLimits function inside packages/lib/server/checkBookingLimits.ts is invoked only for the first occurrence. The remaining 4 are inserted without the check being re-run against the accumulating count.
  • No error is returned — the caller receives 201 Created for every occurrence.
  • The host's calendar ends up with 5 bookings on the same day, far exceeding the configured limit.

Expected Results

  • Booking limits should be evaluated for every individual occurrence before any of them are persisted.
  • If any single occurrence would push the cumulative total over the configured limit for that period (day / week / month / year), the entire recurring request should be rejected with a 400 Bad Request, for example:
{
  "status": "error",
  "error": {
    "message": "Booking limit of 1 per day exceeded for one or more of the requested recurring dates."
  }
}
  • The same booking limits enforced on the UI booking page should behave identically when bookings are created through the API v2.

Technical Details

  • Affected endpoint: POST /v2/bookings (API v2)
  • Relevant source files to investigate:
    • apps/api/v2/src/modules/bookings/services/bookings.service.ts — iterates over recurring occurrences and calls the booking handler per date
    • packages/lib/server/checkBookingLimits.ts — performs the per-period count check
    • packages/features/bookings/lib/handleNewBooking/index.ts — core booking handler invoked for each occurrence
  • Root cause: The recurring occurrence loop calls handleNewBooking sequentially per date. Each call re-queries the DB for the current booking count from scratch, so earlier iterations that were inserted in the same in-flight request are either not yet visible (transaction isolation) or not accumulated in memory — meaning the limit counter never increments across the loop, and every iteration passes the same stale check.
  • Node.js version: Reproducible on Node.js 18.x and 20.x.
  • Related issue: POST v2/bookings - recurrenceCount (with eventType reoccuring enabled) causes lengthInMinutes to be ignored #23022 (lengthInMinutes is ignored for recurring bookings via API v2), which confirms the recurring path in the v2 API is broadly under-validated.

Evidence

Reproduced on a self-hosted main branch instance (Docker Compose). Created an event type with "Max 1 booking per day" enabled. Sent POST /v2/bookings with recurrenceCount: 5. The API returned 201 Created for all 5 occurrences and GET /v2/bookings confirmed 5 records on the same day. Then attempted to book a second individual 30-minute slot on the same day via the UI booking page — that correctly returned the "Booking limit reached" error, confirming the limit logic works on the UI path but is bypassed via the API v2 recurring path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    🐛 bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions