---
title: Cancel a recurring series
description: "Cancels every FUTURE active occurrence of the recurring series containing this booking (past occurrences are the audit trail — the Cal.com pattern)."
api_method: POST
api_path: "/v1/bookings/{uid}/series/cancel"
canonical_url: https://wiblo.app/docs/developers/api/series/cancel-booking-series
last_updated: 2026-07-28T18:08:25+02:00
md_url: https://wiblo.app/docs/developers/api/series/cancel-booking-series.md
---

# Cancel a recurring series

`POST /v1/bookings/{uid}/series/cancel`

Cancels every FUTURE active occurrence of the recurring series containing this booking (past occurrences are the audit trail — the Cal.com pattern). Each cancelled row takes the house cancel shape: status → `cancelled`, `cancelled_by` records the acting member, the optional `reason` lands in `cancellation_reason`, `ical_sequence` jumps by 100, and the idempotency key is KEPT. Same authorization as the single cancel, checked on this booking. A non-recurring booking answers 400. Per-user rate-limited at 30/60s.

## Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `uid` | `string` | Yes |  |

## Headers

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `X-Wiblo-Workspace` | `string` | Yes | Workspace id the call is scoped to. The actor must hold an active membership in it; a foreign or unknown id collapses onto 404 WORKSPACE_NOT_FOUND. |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `reason` | `string` | No | Optional cancellation reason (max 500 characters); it lands in each cancelled row's `cancellation_reason`. |

## Request

**curl**

```bash
curl https://api.wiblo.app/v1/bookings/2b6d9f3c-0e5a-4d7b-9c4f-8a3e6b0d2f5c/series/cancel \
  -X POST \
  -H "Authorization: Bearer $WIBLO_TOKEN" \
  -H "X-Wiblo-Workspace: $WIBLO_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Client is pausing coaching for the autumn"
}'
```

**TypeScript**

```ts
import { createSdk, cancelBookingSeries } from "@workspace/sdk"

const sdk = createSdk({ baseUrl: "https://api.wiblo.app" })

const { data, error } = await cancelBookingSeries({
  client: sdk,
  path: { uid: "2b6d9f3c-0e5a-4d7b-9c4f-8a3e6b0d2f5c" },
  headers: { "X-Wiblo-Workspace": "..." },
  body: {
    "reason": "Client is pausing coaching for the autumn"
  },
})
```

## Example response

```json
{
  "series": {
    "recurring_event_id": "e7b3f9d1-2a6c-4d8b-9f4e-5c1a8d3b7e2f",
    "cancelled_count": 4,
    "message": "4 future bookings cancelled"
  }
}
```

## Responses

**`200`** — The series was cancelled. `cancelled_count` is how many rows this call flipped (0 when everything was already cancelled). Returns `CancelBookingSeriesResponse`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `series` | `object` | Yes | The series-cancel outcome. |
| `series.recurring_event_id` | `string` | Yes | The shared id of the cancelled series. |
| `series.cancelled_count` | `integer` | Yes | How many rows this call flipped to `cancelled` (`0` when everything was already cancelled). Future occurrences only — past rows are the audit trail. |
| `series.message` | `string` | Yes | Human-readable outcome message. |

**`400`** — Body failed Zod validation, this booking is not part of a recurring series (`BAD_REQUEST`), or the `X-Wiblo-Workspace` header was missing (`VALIDATION_FAILED`). Returns `ApiErrorEnvelope`.

**`401`** — No valid session cookie or `wbl_*` bearer was present. Returns `ApiErrorEnvelope`.

**`403`** — The actor is neither the booking's organizer, one of its hosts, nor an owner/admin (`BOOKING_FORBIDDEN`). Returns `ApiErrorEnvelope`.

**`404`** — No booking with this uid in this workspace (`BOOKING_NOT_FOUND`) or the workspace collapsed (`WORKSPACE_NOT_FOUND`). Returns `ApiErrorEnvelope`.

**`429`** — Rate limit exceeded. The body's `error.code` is `RATE_LIMITED` and `error.details.retry_after` is the same number of seconds as the `Retry-After` header. Returns `ApiErrorEnvelope`.
